像其他人一样说,这取决于目标和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
某处),并在doit2
的overflowme
最后一个元素之间的堆栈。编译器也可以自由地将任何其他东西放在堆栈上,所以谁知道还有什么可以在那里。
problem2
的局部变量int x
可以进入寄存器或栈中。在没有优化的情况下编译时,局部变量通常会进入堆栈,即使它们可以进入寄存器。
所以,让我们假设有doit2
的overflowme
阵列,旧的帧指针,返回地址,并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作为帧基本指针,也可以认为堆栈帧存在,但是帧不会被分帧。
不,每次37个作品! – user133466 2010-05-21 22:26:56
这是功课吗? – 2010-05-21 22:27:31
它不一定 - 这只会发生在特定的编译器,CPU,编译器标志等。 – 2010-05-21 22:27:43