2010-05-21 147 views
0
int main(void) { 
    problem2(); 
} 

void doit2(void) { 
    int overflowme[16]; 
    //overflowme[37] =0; 
} 

void problem2(void) { 
    int x = 42; 
    doit2(); 
    printf("x is %d\n", x); 
    printf("the address of x is 0x%x\n", &x); 
} 

有人能帮我理解为什么overflowme [37] = 0;从doit2函数会覆盖x的值? (请在你的解释中包含函数doit2的程序计数器和帧指针)谢谢!堆栈溢出技术

它可以在x86 windows计算机上正常工作(ok!),并使用Project properties-> Configuration properties-> C/C++ - > Code Generation-> Basic Runtime Checks设置为“Default”。所以它不是一个未定义的行为。

+0

不,每次37个作品! – user133466 2010-05-21 22:26:56

+2

这是功课吗? – 2010-05-21 22:27:31

+4

它不一定 - 这只会发生在特定的编译器,CPU,编译器标志等。 – 2010-05-21 22:27:43

回答

5

像其他人一样说,这取决于目标和compliler的,但对你的那些保持不变,而且没有在代码的任何其他东西,看起来像他们引入随机性堆栈(相对说话),所以它每次都会做同样的事情。

系统堆栈通常从高地址向低地址增长。如果堆栈指针是0x1234并且你推入一个值(在32位{4字节}系统上),那么堆栈指针将变为0x1230。

数组从最低地址到最高地址进行寻址。如果您有

char a[2]; 

并且[0]位于0x0122,则[1]将位于0x0123。

doit2中的数组是一个自动变量,意味着它在创建函数时创建,并在函数退出时删除。自动变量必须存在于堆栈或寄存器中。由于它是一个数组,因此编译器将其放入RAM而不是寄存器会复杂得多(这使索引变得更容易,因为它只是将索引*大小添加到数组第一个成员的地址)。由于堆栈在RAM中,因此编译器会将数组放入堆栈。

为该数组分配堆栈空间意味着堆栈指针为sizeof(int)*16,比如果该数组不存在时小。堆栈指针最有可能指向overflowme[0]而在doit2

还有其他的东西可能在堆栈上,还有一些东西必须放在堆栈上。必须在堆栈上的东西是在函数被调用时被推到那里的返回指针。在32位系统上,每个应占用4个字节。可能在堆栈上的东西(如果编译器想要使用它的话)是前一帧指针。 (显式*)x86-32上的堆栈帧仅仅是ESP和EBP之间的空间,但它们并不是必需的,因此它们通常不被使用,而EBP只是用作通用寄存器(可用的更多通用寄存器是一般很好)。 尽管使用堆栈帧很有用,因为它们使调试变得更加容易,因为ESP和EBP充当本地变量边缘的标记。有时需要堆栈帧,例如当您使用alloca或C99的可变大小的自动数组时,因为它们允许函数的局部变量空间被mov EPB, ESP或等效的instructiosn而不是sub size_of_local_variable, ESP丢弃,所以编译器不必知道大小的框架。它们还允许局部变量相对于EBP被寻址,而不是在alloca的情况下变化的ESP。在这种情况下,EBP直到当前函数结束才会更改,除非通过调用函数进行更改和恢复。

编译时没有优化打开编译器通常总是使用堆栈帧,因为它们使调试更容易。使用堆栈框架对代码进行建模,然后在证明它们不是必需的之后,将代码转换为不使用它们也更容易。

所以,EBP的前值可能会或可能不会驻留在返回地址(在problem2某处),并在doit2overflowme最后一个元素之间的堆栈。编译器也可以自由地将任何其他东西放在堆栈上,所以谁知道还有什么可以在那里。

problem2的局部变量int x可以进入寄存器或栈中。在没有优化的情况下编译时,局部变量通常会进入堆栈,即使它们可以进入寄存器。

所以,让我们假设有doit2overflowme阵列,旧的帧指针,返回地址,并problem2“堆叠在S x(并且在某些更多的东西{这是真正在较高地址}它)。

由于&(overflowme[i])相同的overflowme第一元素的地址的加法,以(I * {的int大小})和旧EBP位于的overflowme的最后一个元素和一个返回地址后的旧后位于EBP和int x位于返回地址之后,x肯定会被缓冲区溢出的方式阻止。

为什么发生这种情况对于37的指数还不清楚。指针数学(假设只有上面列出的数据位于数组和x之间的堆栈中)并不表明它应该基于4字节指针(32位机器),但如果这是8字节指针系统( 64位机器),那么数学更接近我期望x的地址,如果是sizeof(int) == 8。编译器也可以继续前进,并为printf(格式字符串必须放在堆栈中后的可变参数)分配堆栈空间,这会影响数学运算(也鼓励编译器将x放置在堆栈上,因为它会无论如何都必须推它)。

如果您想要更详细地回答您的问题,请查看此代码的程序集并计算出确切的地址。

  • 即使没有使用EBP作为帧基本指针,也可以认为堆栈帧存在,但是帧不会被分帧。
+0

ggrrrrr。很好的答案.. – 2011-05-04 13:38:14

3

它可能不是。变量在堆栈中的位置依赖于编译器和平台。

+0

每次使用项目属性 - >配置属性 - > C/C++ - >代码生成 - >基本运行时检查设置为“默认” – user133466 2010-05-21 22:27:36

+8

@metashockwave:仅仅因为它适用于** 1 **非常特定的平台/编译器/ etc ,这并不意味着它通常是真的 – 2010-05-21 22:28:58

+0

不,这些设置是什么,但听起来像一些Windows的东西,最有可能的是x86的。 – johannes 2010-05-21 22:29:43

0

它并不:

x is 42 
the address of x is 0xbff9ea1c 

以上发生的每一次上一个真正的编译器和平台(地址变更),让您明明白白是正确的,它不是不确定的行为。

+1

我认为你已经正确地击中了那里的头部。 – 2010-05-22 03:11:20

2

你的筹码将会是这个样子:

char overflowme[16] 
return address to problem2() from calling doit2() 
parameters for doit2(), if it had any 
int x = 42 
return address to main() from calling problem2() 
parameters for problem2(), if it had any 
local variables for main(), if it had any 

当您向overflowme[37],你会走过去的overflowme末(因为它只有16字节)和以往的返回地址从doit2()呼叫并覆盖x

正如其他人所提到的,这高度依赖于平台和编译器,但应该给你一个很好的可视化问题。尝试通过调试窗口打开代码并显示堆栈。