【问题标题】:Is the stack growing the wrong way when you compile C code with GCC使用 GCC 编译 C 代码时堆栈是否以错误的方式增长
【发布时间】:2016-06-22 12:43:31
【问题描述】:

我有以下 C 代码:

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
   int value = 5;
   char buffer_one[8], buffer_two[8];
   strcpy(buffer_one, "one"); /* Put "one" into buffer_one. */
   strcpy(buffer_two, "two"); /* Put "two" into buffer_two. */

   return 0;
}

根据我对堆栈的了解,buffer_one 数组应该从比 buffer_two 数组更高的地址开始,因为堆栈向着较低的地址增长,而 buffer_two 位于堆栈的顶部。但是,当我使用 gcc 编译代码并使用 GDB 单步执行代码时,它告诉我情况正好相反:

eman@eman:~/Documents/CFiles/bufferOverflow/source$ gcc -g -o example overflow_example.c 
eman@eman:~/Documents/CFiles/bufferOverflow/source$ gdb -q example
Reading symbols from example...done.
(gdb) list
1   #include <stdio.h>
2   #include <string.h>
3   int main() {
4   char buffer_one[8];
5   char buffer_two[8];
6   
7   strcpy(buffer_one, "one"); /* Put "one" into buffer_one. */
8   strcpy(buffer_two, "two"); /* Put "two" into buffer_two. */
9   
10  return 0;
(gdb) break 9
Breakpoint 1 at 0x400571: file overflow_example.c, line 9.
(gdb) run
Starting program: /home/eman/Documents/CFiles/bufferOverflow/source/example 

Breakpoint 1, main () at overflow_example.c:10
10  return 0;
(gdb) print &buffer_one
$1 = (char (*)[8]) 0x7fffffffdd50
(gdb) print &buffer_two
$2 = (char (*)[8]) 0x7fffffffdd60
(gdb) 

这里发生了什么?

额外问题:为什么数组初始化为8字节,却占用了10字节?

【问题讨论】:

  • 提示:不是 10 个字节,而是 0x10 个字节。
  • @Will 哦,16 个字节?我认为每个字符占用 1 个字节的内存而不是 2
  • 很难说它是错的,因为没有正确的标准。
  • @Carefullcars,是什么让您认为存在堆栈?如果有一个堆栈,是什么让你认为它的增长方向是正确的?如果有堆栈,是什么让您认为函数的局部变量接收地址就好像它们按照声明的顺序被压入堆栈一样? 没有是 C 规定的。
  • 假设一个底层 x64 架构,在你的例子中,gcc 编译器和一个基于 linux 的操作系统,堆栈是一个reverse access 数据映射。因此分配将向最高逻辑地址执行,访问将向最低逻辑地址执行。无论如何,据我所知,C 标准中没有堆栈的概念。

标签: c gcc gdb


【解决方案1】:

几件事:

  1. 编译器不需要按声明变量的顺序(或任何其他特定顺序)布置变量。 struct 实例的成员按照声明的顺序排列,尽管每个成员之间可能存在填充字节(参见 2)。

  2. 平台可能要求对象在特定地址上对齐(例如,可被 8 或 16 整除的地址)。因此,对象之间可能存在未使用的字节。如果buffer_onebuffer_two的地址相差16,这并不意味着编译器为buffer_one预留了16个字节,这意味着buffer_one的结尾和开头之间有8个字节的填充buffer_two,并且尝试读/写这些字节的行为是未定义的。实际上,这意味着您的代码可以容忍buffer_one 中的小缓冲区溢出而没有不良影响,但您不想依赖它。

  3. 您不能依赖对象之间的顺序才有意义。您当然不能依赖它在实现中可重复。

  4. 虽然您很难找到不使用 C 实现的 C 实现,但语言定义并不要求使用运行时堆栈,也不需要要求堆栈向上(朝向增加的地址)或向下(朝向减少的地址)增长。这只是一个实现细节。

【讨论】:

  • 很好的答案,明白了很多!我正在关注暗示 C 编译器将始终以相同方式将对象添加到堆栈帧的书籍(可能是为了简化)。如果我真的想了解它如何设置堆栈帧(如果堆栈存在 <. c>
猜你喜欢
  • 2022-01-04
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 2016-12-21
  • 2014-09-09
  • 2015-01-09
  • 2012-10-08
  • 2011-03-23
相关资源
最近更新 更多