编译器如何在不知道大小的情况下分配内存?

我写了一个C程序,它接受来自用户的整数input,用作整型数组的大小,并使用该值声明给定大小的数组,并通过检查数组大小来确认它。

码:

#include <stdio.h> int main(int argc, char const *argv[]) { int n; scanf("%d",&n); int k[n]; printf("%ld",sizeof(k)); return 0; } 

令人惊讶的是这是正确的! 该程序能够创build所需大小的数组。
但是所有的静态内存分配都是在编译时完成的,在编译期间n的值是未知的,那么编译器如何才能分配所需大小的内存呢?

如果我们可以像这样分配所需的内存,那么使用malloc()calloc()进行dynamic分配有什么用处呢?

这不是“静态内存分配”。 您的数组k是一个可变长度数组(VLA),这意味着该数组的内存在运行时分配。 大小将由n的运行时间值决定。

语言规范并没有规定任何特定的分配机制,但是在一个典型的实现中,你的k通常最终是一个简单的int *指针,实际的内存块在运行时被分配到堆栈上。

对于VLA sizeof运算符也在运行时进行评估,这就是为什么您在实验中从中获得正确值的原因。 只需使用%zu (不是%ld )来输出size_ttypes的值。

malloc (和其他dynamic内存分配函数)的主要目的是覆盖适用于本地对象的基于范围的生存期规则。 即与malloc分配malloc仍然分配“永远”,或直到你明确地释放它的free 。 使用malloc分配的malloc不会在块的末尾自动释放。

在你的例子中,VLA不提供这个“范围失效”function。 你的数组k仍然服从规则的基于范围的生命周期规则:它的生命周期在块的末尾结束。 因此,在一般情况下,VLA不可能替代malloc和其他dynamic内存分配函数。

但在特定的情况下,当你不需要“打败范围”,只是使用malloc来分配一个运行时大小的数组,VLA可能确实被视为malloc的替代品。 再次牢记,VLA通常被分配到堆栈上,并且在堆栈上分配大量内存仍然是一个相当有问题的编程习惯。

在C中,编译器支持VLA(可变长度数组)的方法取决于编译器 – 它不必使用malloc() ,并且可以(通常也使用)有时称为“堆栈”例如使用不是标准C的一部分的系统专用函数(如alloca() 。如果使用堆栈,则数组的最大大小通常比使用malloc()可能小得多,因为现代操作系统允许程序更小堆栈内存的配额。

可变长度数组的内存显然不能静态分配。 但它可以分配在堆栈上。 一般来说,这涉及到使用“帧指针”来跟踪堆栈指针的dynamic确定的改变的function堆栈帧的位置。

当我尝试编译你的程序时,看起来实际上发生的是变长数组被优化了。 所以我修改你的代码来强制编译器实际分配数组。

 #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,因为我可以阅读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]对于使用“向零增长”的堆栈的典型处理器。

当编译器为编译器分配内存给variables ,这意味着这些variables的位置被确定并embedded到编译器生成的可执行代码中,而不是编译器在工作时为其提供空间。 实际的dynamic内存分配在运行时由生成的程序执行。