[英]How does the compiler allocate memory without knowing the size at compile time?
我寫了一個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()
的動態分配有什么用?
這不是“靜態內存分配”。 您的數組k
是可變長度數組(VLA),這意味着此數組的內存是在運行時分配的。 大小將由n
的運行時間值確定。
語言規范沒有規定任何特定的分配機制,但在典型的實現中,你的k
通常最終會成為一個簡單的int *
指針,實際的內存塊在運行時被分配在堆棧上。
對於VLA sizeof
運算符也在運行時進行評估,這就是您在實驗中從中獲取正確值的原因。 只需使用%zu
(不是%ld
)打印size_t
類型的值。
malloc
(和其他動態內存分配函數)的主要目的是覆蓋適用於本地對象的基於范圍的生存期規則。 即使用malloc
分配的malloc
仍然“永久”分配,或直到您使用free
顯式釋放它。 使用malloc
分配的malloc
不會在塊結束時自動釋放。
在您的示例中,VLA不提供這種“范圍失敗”功能。 您的數組k
仍然遵循常規的基於范圍的生命周期規則:其生命周期在塊的結尾處結束。 因此,在一般情況下,VLA不可能替換malloc
和其他動態內存分配函數。
但是在特定情況下,當您不需要“擊敗范圍”並僅使用malloc
來分配運行時大小的數組時,VLA可能確實被視為malloc
的替代品。 請記住,VLA通常是在堆棧上分配的,並且到目前為止在堆棧上分配大塊內存仍然是一個相當可疑的編程實踐。
在C中,編譯器支持VLA(可變長度數組)的方法取決於編譯器 - 它不必使用malloc()
,並且可以(並且經常)使用有時稱為“堆棧”的內存 -例如,使用不屬於標准C的系統特定函數(如alloca()
。如果它確實使用堆棧,則數組的最大大小通常比使用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使用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 "%d\000"
.LC1:
.ascii "%s %ld\000"
此構造的內存(稱為“可變長度數組”,VLA)以與alloca
類似的方式分配在堆棧上。 具體如何發生這取決於你正在使用哪個編譯器,但實際上它是在知道它時計算大小的情況,然后從堆棧指針中減去[1]總大小。
你確實需要malloc
和朋友,因為當你離開這個功能時,這個分配會“死亡”。 [它在標准C ++中無效]
[1]對於使用“向零增長”的堆棧的典型處理器。
當說編譯器在編譯時為變量分配內存時 ,意味着這些變量的位置決定並嵌入編譯器生成的可執行代碼中,而不是編譯器在它工作時為它們提供空間。 。 實際的動態內存分配由生成的程序在運行時執行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.