【问题标题】:How a variable sized array in C work internally?C中的可变大小数组如何在内部工作?
【发布时间】:2020-10-11 06:32:40
【问题描述】:

我正在尝试了解可变大小的静态数组如何在内部工作:

以下是 C 中固定大小的静态数组及其对应的汇编;

int main()
{
int arr[2] = {3};
}
================
main:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], 0
        mov     DWORD PTR [rbp-8], 2
        mov     eax, 0
        pop     rbp
        ret

然而,一个可变大小的数组如下所示

int main()
{
int varSize ;
int Arr[varSize];
}
=================
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     rax, rsp
        mov     rcx, rax
        mov     eax, DWORD PTR [rbp-4]
        movsx   rdx, eax
        sub     rdx, 1
        mov     QWORD PTR [rbp-16], rdx
        movsx   rdx, eax
        mov     r8, rdx
        mov     r9d, 0
        movsx   rdx, eax
        mov     rsi, rdx
        mov     edi, 0
        cdqe
        lea     rdx, [0+rax*4]
        mov     eax, 16
        sub     rax, 1
        add     rax, rdx
        mov     edi, 16
        mov     edx, 0
        div     rdi
        imul    rax, rax, 16
        sub     rsp, rax
        mov     rax, rsp
        add     rax, 3
        shr     rax, 2
        sal     rax, 2
        mov     QWORD PTR [rbp-24], rax
        mov     rsp, rcx
        mov     eax, 0
        leave
        ret

如果我声明一个可变大小的数组,我会看到大量的汇编指令。谁能解释一下这种可变大小的灵活性是如何实现的?

【问题讨论】:

  • 战术错误:您无法确定这些说明的用途。将 varSize 设置为某个非常量值,例如 varSize = f();。在数组中存储一些东西,比如 Arr[20] = 3;这样您就可以了解说明的作用。
  • 你显然没有优化编译,所以代码是不必要的复杂。无论如何,变量版本所做的是将数组大小四舍五入为 16 字节的倍数以保持堆栈对齐,然后通过调整堆栈指针来分配它。静态版本使用 128 字节的红色区域。使用更大但仍然是静态的数组,它也可以调整堆栈指针,但具有编译时间常数。
  • 目前的代码是荒谬的,因为数组的大小取自一个未初始化的变量。
  • Here 是一个更好的例子,它也可以在启用优化中幸存下来。您应该会发现这更容易理解发生了什么。

标签: arrays assembly gcc variable-length-array compiler-specific


【解决方案1】:

alloca() 相同的机制 - 通过减少堆栈指针来分配内存,假设堆栈足够大和/或操作系统会根据需要增加它。

当请求的大小超过内存页并且堆栈接近其末尾时,可能会出现一些问题。通常,操作系统通过在堆栈顶部设置保护页面并监视该区域中的故障来增长堆栈,但这假设堆栈或多或少是按顺序增长的(通过推送和函数调用)。如果减少的堆栈指针超出保护页,它可能最终指向一个虚假的位置。我不确定编译器对这种可能性做了什么。

【讨论】:

  • 只有 Windows 要求每个中间页面都被触摸(堆栈探测)。 Linux allows 堆栈增长在初始线程堆栈的 8MiB(默认情况下)区域中的任何位置。 (额外的线程需要完全预分配的线程堆栈)。请参阅Linux process stack overrun by local variables (stack guarding),其中还描述了gcc -fstack-check 所做的事情:即使在不需要它的目标上也始终进行堆栈探测,以防御堆栈冲突攻击,其中巨大的 VLA / alloca 可以一直跳转到其他映射。
猜你喜欢
  • 1970-01-01
  • 2013-10-22
  • 1970-01-01
  • 2019-12-02
  • 2018-07-21
  • 1970-01-01
  • 2012-03-12
  • 1970-01-01
相关资源
最近更新 更多