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

Rah*_*hul 66 c memory arrays c99 variable-length-array

我写了一个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;
}
Run Code Online (Sandbox Code Playgroud)

而且令人惊讶的是它是正确的!该程序能够创建所需大小的数组.
但是所有静态内存分配都是在编译时完成的,并且在编译期间,其值n是未知的,那么为什么编译器能够分配所需大小的内存呢?

如果我们能够分配所需的内存就是这样,然后有什么用使用动态分配的malloc()calloc()

AnT*_*AnT 73

这不是"静态内存分配".您的数组k是可变长度数组(VLA),这意味着此数组的内存在运行时分配.大小将由运行时值决定n.

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

对于VLA sizeof运算符,也会在运行时进行评估,这就是您在实验中从中获取正确值的原因.只需使用%zu(不%ld)打印类型的值size_t.

malloc(和其他动态内存分配函数)的主要目的是覆盖适用于本地对象的基于范围的生存期规则.即分配的内存malloc仍然是"永久"分配的,或直到你明确地释放它为止free.分配的内存malloc不会在块结束时自动释放.

在您的示例中,VLA不提供这种"范围失败"功能.您的阵列k仍然遵循常规的基于范围的生命周期规则:其生命周期在块的末尾结束.因此,在一般情况下,VLA不可能替换malloc和其他动态内存分配功能.

但是在特定情况下,当您不需要"击败范围"并且只是用于malloc分配运行时大小的阵列时,VLA可能确实被视为替代malloc.请记住,VLA通常是在堆栈上分配的,并且到目前为止在堆栈上分配大块内存仍然是一个相当可疑的编程实践.

  • @Rahul:C不支持`static`VLAs.如果`n`是运行时值,则不允许使用`static int k [n]`.但即使它被允许,它也不会每次都分配一个新的内存块.与此同时.`malloc`每次调用时都会分配一个新块.因此,即使使用`static`,与malloc`也没有相似之处. (5认同)
  • @Rahul:VLA是在C99标准中引入的.在形式上,他们已经存在了大约18年.当然,编译器支持需要一些时间. (4认同)
  • 好吧,我绝对*不会*调用这个*`int*`指针",因为这里没有这样的C对象. (3认同)
  • "在一个典型的实现中,你的k将最终成为一个简单的int*指针"的措辞似乎有点冒险.关于指针和数组的问题有很多混淆. (2认同)
  • @rcgldr:VLA与`alloca`非常相似,肯定是"alloca"的"灵感".但是,`alloca`使用"function"生命周期分配内存:内存一直存在,直到函数退出.也就是说它忽略了除最外层之外的所有块边界 - 函数体本身.同时,VLA具有正常的基于块的寿命.在这方面,VLA与`alloca`非常不同.确实,Visual Studio到目前为止还不支持VLA.但是,由于C11 VLA是该语言的*可选*功能,所以没有任何价值. (2认同)

Pet*_*ter 11

在C中,编译器支持VLA(可变长度数组)的方法取决于编译器 - 它不必使用malloc(),并且可以(并且经常)使用有时称为"堆栈"的内存 - 例如使用系统这样的特定函数alloca()不是标准C的一部分.如果它确实使用堆栈,则数组的最大大小通常比使用的小得多malloc(),因为现代操作系统允许程序的堆栈内存配置小得多.

  • 现代操作系统(在某些情况下甚至是旧的和嵌入式操作系统)允许用户配置堆栈大小 (2认同)

plu*_*ash 10

可变长度数组的内存显然不能静态分配.然而,它可以在堆栈上分配.通常,这涉及使用"帧指针"来在动态确定的对堆栈指针的改变的情况下跟踪功能堆栈帧的位置.

当我尝试编译你的程序时,似乎实际发生的是可变长度数组被优化了.所以我修改了你的代码以强制编译器实际分配数组.

#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;
}
Run Code Online (Sandbox Code Playgroud)

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"
Run Code Online (Sandbox Code Playgroud)