Как компилятор выделяет память, не зная размер во время компиляции?
Я написал программу C, которая принимает целочисленный ввод от пользователя, который используется как размер целочисленного массива, и с помощью этого значения он объявляет массив заданного размера, и я подтверждаю это, проверяя размер массива.
код:
#include <stdio.h>
int main(int argc, char const *argv[])
{
int n;
scanf("%d",&n);
int k[n];
printf("%ld",sizeof(k));
return 0;
}
и на удивление это правильно! Программа способна создать массив нужного размера.
Но все статическое выделение памяти выполняется во время компиляции, а во время компиляции значение n не известно, так как компилятор может выделить память нужного размера?
если мы можем выделить необходимую память просто так, то в чем польза динамического выделения с помощью malloc() и calloc()?
5 ответов:
это не "статическое выделение памяти". Ваш массив
kявляется массивом переменной длины (VLA), что означает, что память для этого массива выделяется во время выполнения. Размер будет определяться значением времени выполненияn.спецификация языка не диктует какой-либо конкретный механизм распределения, но в типичной реализации ваш
kобычно в конечном итоге будет простойint *указатель с фактическим блоком памяти, выделяемым в стеке при запуске время.для вла
sizeofоператор также вычисляется во время выполнения, поэтому вы получаете от него правильное значение в своем эксперименте. Просто используйте%zu(не%ld) для печати значений типаsize_t.основная цель
malloc(и другие функции динамического выделения памяти)-это переопределение правил времени жизни на основе области, которые применяются к локальным объектам. То есть память выделяется сmallocостается выделенным "навсегда", или пока вы явно освободите его с помощьюfree. Память, выделенная с помощьюmallocне получает автоматически освобождается в конце блока.VLA, как и в вашем примере, не предоставляет эту функцию "поражения области". Ваш массив
kпо-прежнему подчиняется регулярным правилам жизненного цикла на основе области: его срок службы заканчивается в конце блока. По этой причине, в общем случае, VLA не может заменитьmallocи другие функции динамического распределения памяти.но в конкретных случаях, когда вам не нужно "победить область" и просто использовать
mallocчтобы выделить массив размером во время выполнения, VLA действительно может рассматриваться как заменаmalloc. Просто имейте в виду, опять же, что VLAs обычно выделяются в стеке и выделение больших кусков памяти в стеке по сей день остается довольно сомнительной практикой программирования.
В C средство, с помощью которого компилятор поддерживает VLAs (массивы переменной длины), зависит от компилятора - он не должен использовать
malloc(), и может (и часто делает) использовать то, что иногда называют "стековой" памятью - например, используя системные функции, такие какalloca()которые не являются частью стандарта C. Если он использует стек, максимальный размер массива обычно намного меньше, чем это возможно с помощьюmalloc(), потому что современные операционные системы позволяют программам гораздо меньшую квоту стека память.
память для массивов переменной длины явно не может быть статически выделена. Однако он может быть выделен в стеке. Обычно это включает в себя использование "указателя кадра" для отслеживания местоположения кадра стека функций перед динамически определенными изменениями указателя стека.
когда я пытаюсь скомпилировать вашу программу, кажется, что на самом деле происходит то, что массив переменной длины был оптимизирован. Поэтому я изменил ваш код, чтобы заставить компилятор фактически выделите массив.
#include <stdio.h> int main(int argc, char const *argv[]) { int n; scanf("%d",&n); int k[n]; printf("%s %ld",k,sizeof(k)); return 0; }godbolt компиляция для arm с помощью gcc 6.3 (используя arm, потому что я могу читать arm ASM) компилирует это в https://godbolt.org/g/5ZnHfa. (комментарии мои)
main: push {fp, lr} ; Save fp and lr on the stack add fp, sp, #4 ; Create a "frame pointer" so we know where ; our stack frame is even after applying a ; dynamic offset to the stack pointer. sub sp, sp, #8 ; allocate 8 bytes on the stack (8 rather ; than 4 due to ABI alignment ; requirements) sub r1, fp, #8 ; load r1 with a pointer to n ldr r0, .L3 ; load pointer to format string for scanf ; into r0 bl scanf ; call scanf (arguments in r0 and r1) ldr r2, [fp, #-8] ; load r2 with value of n ldr r0, .L3+4 ; load pointer to format string for printf ; into r0 lsl r2, r2, #2 ; multiply n by 4 add r3, r2, #10 ; add 10 to n*4 (not sure why it used 10, ; 7 would seem sufficient) bic r3, r3, #7 ; and clear the low bits so it is a ; multiple of 8 (stack alignment again) sub sp, sp, r3 ; actually allocate the dynamic array on ; the stack mov r1, sp ; store a pointer to the dynamic size array ; in r1 bl printf ; call printf (arguments in r0, r1 and r2) mov r0, #0 ; set r0 to 0 sub sp, fp, #4 ; use the frame pointer to restore the ; stack pointer pop {fp, lr} ; restore fp and lr bx lr ; return to the caller (return value in r0) .L3: .word .LC0 .word .LC1 .LC0: .ascii "%d0" .LC1: .ascii "%s %ld0"
память для этой конструкции, которая называется "массив переменной длины", VLA, выделяется в стеке аналогично
alloca. Точно, как это происходит, зависит от того, какой именно компилятор вы используете, но по существу это случай вычисления размера, когда он известен, а затем вычитания [1] общего размера из указателя стека.вам нужно
mallocи друзьями, потому что это распределение "умирает", когда вы покидаете функцию. [И это не действует в стандарте C++][1] для типичных процессоров, которые используют стек, который "растет к нулю".
когда говорят, что компилятор выделяет память для переменных в время компиляции, это означает, что размещение этих переменных определяется и встроено в исполняемый код, который генерирует компилятор, а не то, что компилятор делает пространство для них доступным во время работы. Фактическое динамическое выделение памяти осуществляется сгенерированной программой при ее запуске.
Comments