代码之家  ›  专栏  ›  技术社区  ›  Rahul

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

  •  66
  • Rahul  · 技术社区  · 7 年前

    代码:

    #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 回复  |  直到 7 年前
        1
  •  73
  •   AnT stands with Russia    7 年前

    这不是“静态内存分配”。您的阵列 k n .

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

    对于VLA sizeof %zu (不是 %ld )打印类型的值 size_t

    其主要目的是 malloc 马洛克 free . 分配的内存 不会在块结束时自动取消分配。

    与您的示例一样,VLA不提供这种“破坏范围”的功能。您的阵列 仍然遵循基于作用域的常规生存期规则:其生存期在块的末尾结束。因此,在一般情况下,VLA不可能取代 和其他动态内存分配功能。

    但在特定情况下,当你不需要“打败范围”而只需要使用 马洛克 为了分配一个运行时大小的阵列,VLA实际上可能被视为 马洛克

        2
  •  11
  •   Peter    7 年前

    在C语言中,编译器支持VLAs(可变长度数组)的方式取决于编译器-它不必使用 malloc() alloca() 这不是标准C的一部分。如果它确实使用堆栈,则数组的最大大小通常比使用 malloc()

        3
  •  10
  •   plugwash    7 年前

    可变长度数组的内存显然不能静态分配。但是,它可以在堆栈上分配。通常,这涉及使用“帧指针”来跟踪函数堆栈帧的位置,以应对堆栈指针的动态确定更改。

    当我试图编译你的程序时,实际发生的事情似乎是可变长度数组得到了优化。所以我修改了你的代码,迫使编译器实际分配数组。

    #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;
    }
    

    使用gcc 6.3为arm编译Godbolt(使用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"
    
        4
  •  3
  •   Mats Petersson    7 年前

    该构造的内存称为“可变长度数组”,VLA,在堆栈上分配,分配方式与 alloca . 具体情况取决于您使用的编译器,但本质上是在已知大小的情况下计算大小,然后从堆栈指针中减去[1]总大小。

    malloc 朋友们,因为当你离开这个功能时,这个分配“失效”。[在标准C++中无效]

    [1] 对于使用“向零增长”堆栈的典型处理器。

        5
  •  0
  •   Linkon    7 年前

    编译时间 ,这意味着这些变量的位置是由编译器生成的可执行代码决定并嵌入其中的,而不是编译器在工作时为它们腾出可用空间。 实际的动态内存分配是由生成的程序在运行时执行的。