C

时间:2015-09-22 05:45:13

标签: c memory stack

两个小时前我以为我已经完全理解了堆栈是如何工作的(至少它是如何在C中处理的)。但是我已经注意到我的程序中出现了一些意外的(对我而言)。

我们知道堆栈会朝着更低的内存地址增长(我说的是PC,在我的例子中:Intel 64位,Ubuntu)。因此,当创建新的堆栈帧时,属于该帧的对象具有与之前所有帧相比较低的内存地址。让我感到惊讶的是:帧中的对象具有更高的内存地址。这让我感到震惊,因为我认为先前声明的变量会获得更高的内存地址。

让我用C中的一个例子来说明我的意思。

#include <stdio.h>

void foo()
{
    int firstVar = 1;
    int secondVar = 2;
    printf("firstVar is at: %p\n", &firstVar);
    printf("secondVar is at: %p\n", &secondVar);
}

int main(void)
{
    int mainVar = 0;
    printf("mainVar is at: %p\n", &mainVar);
    foo();
    return 0;
}

使用gcc(-g,-ansi和-pedantic标志)编译后,输出为:

mainVar is at: 0x7ffd1ec0fadc
firstVar is at: 0x7ffd1ec0fab8
secondVar is at: 0x7ffd1ec0fabc

正如所料, mainVar 的内存地址高于 foo()堆栈帧中的内存地址。但是, firstVar 的内存地址低于 secondVar ,即使之前已声明过。查看 foo()的反汇编显示了这种行为:

0x000000000040052d <+0>:    push   %rbp
0x000000000040052e <+1>:    mov    %rsp,%rbp
0x0000000000400531 <+4>:    sub    $0x10,%rsp
0x0000000000400535 <+8>:    movl   $0x1,-0x8(%rbp)
0x000000000040053c <+15>:   movl   $0x2,-0x4(%rbp)
...

1放在2之前的四个字节,再次显示 firstVar 的内存地址低于 secondVar

我的问题是:为什么?根据我读过的所有参考书目,同一堆栈帧中的对象应该具有更高的内存地址。参考书目意味着互联网(例如这个网站)和着名的书籍。我正在使用一个非常标准的系统,所以我怀疑任何ELF或ABI奇怪的东西正在发生......

有什么想法吗?感谢您的阅读。

2 个答案:

答案 0 :(得分:1)

您使用的是哪种编译器?编译器是非常复杂的程序。另外,他们比你更了解C ;-)(在我看来这是一件好事!) 无论如何,他们没有义务遵循您的陈述顺序。你的编译设置是什么?您是否针对速度或尺寸进行了优化?我没假设?

可能发生的事情是,因为您首先使用firstVar(在printf函数中,编译器决定将secondVar置于firstVar之上。{堆栈内存为{ {1}}(在firstVar的堆栈内存之前再次释放)可以更快,更容易地重用,如果需要的话。

如果在函数secondVar中交换前两行会怎样?

答案 1 :(得分:1)

  

根据我读过的所有参考书目,同一堆栈帧中的对象应该具有更高的内存地址,它们声明的时间越早

局部变量放置在堆栈上的顺序决不是标准化的,也不是堆栈帧本身的格式。编译器可以随意分配局部变量,因为它不会影响函数之外的任何内容。 除非将变量返回给调用者,但这不是这种情况。

一个观察结果:

gcc没有优化:

mainVar is at:   000000000022FE4C
firstVar is at:  000000000022FE0C
secondVar is at: 000000000022FE08

gcc -O3完全优化:

mainVar is at:   000000000022FE4C
firstVar is at:  000000000022FE08
secondVar is at: 000000000022FE0C

无论出于何种原因,优化器认为改变分配这两个变量的顺序会有好处。要知道原因,您必须详细研究特定编译器的优化器。这是一种温和有用的知识。

你在这里看不到的是,优化器可能会喜欢将这些变量放在CPU寄存器中。但是没有办法,因为你打印他们的地址和寄存器变量没有地址。通过使用变量的地址,您将强制它在堆栈上分配。

因此,这里要学到的唯一重要的事情是,您不应该编写依赖于堆栈帧的内存布局的代码,也不应该对C标准无法保证的内存布局做任何假设。

如果您需要特定订单,则需要在编译器的喉咙下显示C标准:

typedef struct
{
  int firstVar;
  int secondVar;
} reorder_this_if_you_can;

void foo()
{
    reorder_this_if_you_can re;

    printf("firstVar is at:\t %p\n", &re.firstVar);
    printf("secondVar is at: %p\n", &re.secondVar);
}

现在,无论优化程度如何,订单都会突然得到保证。