Как компилятор выделяет память без знания размера во время компиляции?
Я написал программу на 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()
?
Ответы
Ответ 1
Это не "статическое распределение памяти". Ваш массив k
представляет собой массив переменной длины (VLA), что означает, что память для этого массива распределяется во время выполнения. Размер будет определяться значением времени выполнения n
.
Спецификация языка не определяет какой-либо конкретный механизм выделения, но в типичной реализации ваш k
обычно окажется простым указателем int *
с фактическим блоком памяти, выделяемым в стеке во время выполнения.
Для оператора VLA sizeof
также оценивается во время выполнения, поэтому вы получаете правильное значение от него в своем эксперименте. Просто используйте %zu
(not %ld
) для печати значений типа size_t
.
Основная цель malloc
(и других функций распределения динамической памяти) заключается в переопределении правил жизни, основанных на области, которые применяются к локальным объектам. То есть память, выделенная с помощью malloc
, остается выделенной "навсегда" или пока вы явно не освободите ее с помощью free
. Память, выделенная с помощью malloc
, автоматически не освобождается в конце блока.
VLA, как в вашем примере, не предоставляет эту функциональность с "областью поражения". Ваш массив k
по-прежнему подчиняется регулярным правилам жизни на основе областей: его срок службы заканчивается в конце блока. По этой причине в общем случае VLA не может заменить функции malloc
и другие функции динамической памяти.
Но в особых случаях, когда вам не нужно "побеждать область" и просто используйте malloc
для выделения массива времени выполнения, VLA действительно может рассматриваться как замена для malloc
. Просто имейте в виду, что VLA обычно выделяются в стеке, а выделение больших блоков памяти в стеке по сей день остается довольно сомнительной практикой программирования.
Ответ 2
В C средства, с помощью которых компилятор поддерживает VLA (массивы с переменной длиной), соответствуют компилятору - ему не нужно использовать malloc()
и может (и часто) использовать то, что иногда называют "стек" "память - например используя функции, специфичные для системы, такие как alloca()
, которые не являются частью стандарта C. Если он использует стек, максимальный размер массива обычно намного меньше, чем это возможно при использовании malloc()
, поскольку современные операционные системы позволяют программам гораздо меньшую квоту памяти стека.
Ответ 3
Память для массивов переменной длины явно не может быть статически распределена. Однако он может быть выделен в стеке. Как правило, это связано с использованием "указателя кадра" для отслеживания местоположения фрейма стека функций перед динамически определенными изменениями указателя стека.
Когда я пытаюсь скомпилировать вашу программу, кажется, что на самом деле происходит то, что массив переменной длины был оптимизирован. Поэтому я изменил ваш код, чтобы заставить компилятор фактически выделить массив.
#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 для оружия с использованием gcc 6.3 (с использованием руки, потому что я могу читать 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 "%d\000"
.LC1:
.ascii "%s %ld\000"
Ответ 4
Память для этой конструкции, которая называется массивом переменной длины, VLA, выделяется в стеке, аналогично alloca
. Именно то, как это происходит, зависит от того, какой именно компилятор вы используете, но по существу это случай вычисления размера, когда он известен, а затем вычитает [1] общий размер из указателя стека.
Вам нужно malloc
и друзей, потому что это распределение "умирает", когда вы покидаете функцию. [И это недействительно в стандартном С++]
[1] Для типичных процессоров, которые используют стек, который "растет до нуля".
Ответ 5
Когда говорится, что компилятор выделяет память для переменных в время компиляции, это означает, что размещение этих переменных определяется и внедряется в исполняемый код, который генерирует компилятор, а не то, что компилятор делает пространство для них доступным, пока оно работает.
Фактическое распределение динамической памяти выполняется сгенерированной программой при ее запуске.