C语言 初始化的局部变量如何存储在堆栈内存中?

btxsgosb  于 2022-12-03  发布在  其他
关注(0)|答案(3)|浏览(183)

将局部变量存储到堆栈内存中是否取决于在函数调用中使用它们的值?
在用“C”编程语言做一些简单的练习时,更具体地说--用指针,我注意到关于初始化的局部变量的以下异常(* 我知道这不是异常,只是我缺乏适当的理解 *)。
当我定义和初始化一对变量,并试图打印地址(通过“printf()”函数)的第一个和最后一个变量,我希望地址级数与变量列表的顺序一致。第一个变量具有最高地址,最后一个变量占用(第一个变量的内存块的地址,减去N个内存块,其中N是除第一个变量之外的其余变量的计数)。

v1 = memory block 10;
v2 = memory block 09;
v3 = memory block 08;
v4 = memory block 07;

当我让程序只打印v1和v4的地址时,我期望它打印:

Address of v1 is block 10;
Address of v4 is block 07;

这就是所谓的“异常”。当程序打印这些变量的地址时,它实际上打印:

Address of v1 is block10; (as it should be)
Address of v4 is block09; (isn't v2 supposed to be stored here?)

下面是代码示例:

#include <stdio.h>

int main()
{    
    char a = 1, b = 23, c = 123, d = 12;
        
    printf("address of a: %p\naddress of d: %p", &a, &d);

    return 0;
}

输出为:

address of a: 0x7fffa86fc724
address of d: 0x7fffa86fc725

现在,如果我在printf()函数中添加第三个变量作为参数,第一个变量的地址保持不变,所有三个变量将共享相邻的内存块,遵循定义变量的顺序。让我们取上面的四个变量v1,v2,v3和v4。如果我打印v1,v3和v4的地址,我会得到下面的结果:

v1 = block10;
v3 = block09;
v4 = block08;

在“printf”的参数列表中列出变量地址的顺序()”函数对变量地址的排序没有影响。我可以看到程序仍然遵循定义变量的顺序--首先定义的变量将占用最高地址,而作为参数传递给函数的每个下一个变量将占用内存位置,与前一个变量的内存位置相邻,具体取决于定义的顺序。
程式码范例:

#include <stdio.h>

int main()
{
    char a = 1, b = 23, c = 123, d = 12;
    
    printf("address of c: %p\naddress of a: %p\naddress of d: %p", &c, &a, &d);

    return 0;
}

输出将为:

address of c: 0x7ffd970e27d5
address of a: 0x7ffd970e27d4
address of d: 0x7ffd970e27d6

此外,如果变量至少作为参数传递给函数调用一次(除了“printf()”之外的其他函数),那么只打印v1和v4的地址将产生我最初预期的输出。
程式码范例:

#include <stdio.h>

int main()
{
    char a, b, c, d;
    scanf(" %hhi %hhi %hhi %hhi", &a, &b, &c, &d);    
    
    printf("address of c: %p\naddress of a: %p\naddress of d: %p", &c, &a, &d);

    return 0;
}

输出将为:

address of a: 0x7ffeaac3c4a4
address of d: 0x7ffeaac3c4a7

因此,我得出的结论是,只有作为参数传递给函数调用的变量才会存储在堆栈内存中。发生了什么,更重要的是-为什么会发生这样的事情?
编译器是否(在编译过程中)从程序中“抛出”变量,这些变量尽管被初始化为某个值,但没有被任何函数用作其参数?

lf3rwulv

lf3rwulv1#

根据C标准的规定,只要程序行为不发生变化(假设规则),Optimizer就可以自由地进行优化(这也是未定义行为如此危险的原因,它允许编译器以意想不到的方式“优化”)。
例如,局部变量可以在寄存器中,除非你得到它的地址,迫使编译器把它放在内存中,这样它就有了地址。
volatile强制编译器假设对变量的每次访问(读或写)都会产生副作用,因此禁用了许多优化,并强制变量进入内存(局部变量的堆栈)。这应该可以解决您的问题。

9rnv2umw

9rnv2umw2#

我希望地址顺序与变量列表的顺序一致
为什么?没有人做出这样的保证,K&R书和C标准都没有。此外,堆栈指针可以根据CPU和C允许的情况增加或减少--事实上,C对堆栈什么都没说。我甚至在没有堆栈的微型微控制器上编写过C。
所有三个变量将共享相邻的内存块,遵循定义变量的顺序
同样,没有人做出这样的保证。这只是一个特定系统的一个特定编译器的一个特定行为。

  • C对相邻分配的 * 唯一 * 保证是在使用数组时。
  • C对分配顺序的 * 唯一 * 保证是当你在一个结构体中使用成员变量时。(尽管它们不保证是相邻的。)

因此,我得出的结论是,只有作为参数传递给函数调用的变量才会存储在堆栈内存中。发生了什么,更重要的是-为什么会发生这样的事情?
程序使用的所有变量都必须存储在某个地方,它们不能凭空分配。
至于将变量传递给函数时会发生什么,它们是根据特定系统的ABI(抽象二进制接口)传递的,ABI指定了哪些参数以什么顺序堆叠/存储在寄存器中,或者堆叠是由调用者还是被调用者完成的等等。同样,这是非常特定于系统的,超出了C本身的范围。
这里的主要误解是,你不能研究由一个特定编译器生成的用于特定系统的特定程序,然后得出关于C程序一般如何工作的结论。
然而,您可以得出结论,编译器x如何根据ABI y组织数据和堆栈帧,对于系统z。如果这是您感兴趣的,您的问题需要提到这些细节。

kqlmhetl

kqlmhetl3#

  1. C不知道堆栈的任何信息,自动变量如何存储是由实现决定的。
    1.编译器不需要保留任何变量,如果存在,也不需要将它们存储在特定的位置。程序的可观察行为必须与抽象语义产生的行为完全相同。如何在内部完成这一点取决于实现,但有一个例外:易失性对象。
    因此,您只能讨论特定的实现。当使用不同的编译选项(例如优化级别)时,实现可能会产生不同的代码。
    因此,我得出的结论是,只有作为参数传递给函数调用的变量才会存储在堆栈内存中。发生了什么,更重要的是-为什么会发生这样的事情?
    你的结论是错误的。
void foo(int x)
{
    int z = x;
    printf("%d\n", z);
}

int main(void)
{
    int y = 5;
    foo(y);
}

编码:

.string "%d\n"
foo:
        mov     esi, edi
        xor     eax, eax
        mov     edi, OFFSET FLAT:.LC0
        jmp     printf
main:
        push    rax
        mov     edi, 5
        call    foo
        xor     eax, eax
        pop     rdx
        ret

https://godbolt.org/z/6Ye8WrM8M

相关问题