【问题标题】:Stack memory layout in cc中的堆栈内存布局
【发布时间】:2016-06-23 09:55:30
【问题描述】:

我写了一个小程序来学习C中的堆栈指针。通常堆栈指针指向RAM的最后一个地址。例如,我在 Linux 中运行此代码,堆栈指针位于最后一个地址 (0x7fffffffffff),但变量存储在这些地址中(对于此程序):

c --> 0x7ffc67ca5c94
b --> 0x7ffc67ca5c98
a --> 0x7ffc67ca5c9c

代码:

void fun(int *ptr)
{
   printf("c --> %p\n",ptr);
   ptr++;
   printf("b --> %p\n",ptr);
   ptr++;
   printf("a --> %p\n",ptr);
}

int main() 
{
   int a = 10,b = 20,c = 30;
   printf("%p     %p    %p\n",&a,&b,&c);
   fun(&c);
   return 0;    
}

此程序的输出:

0x7ffc67ca5c9c     0x7ffc67ca5c98    0x7ffc67ca5c94
c --> 0x7ffc67ca5c94
b --> 0x7ffc67ca5c98
a --> 0x7ffc67ca5c9c

我的问题是:

  1. 为什么变量没有存储在堆栈帧的最后一部分 (0x7fffffffffff) 中,它跳过了一些内存位置并被存储?这种行为有什么正当理由吗?

  2. 为什么栈指针地址有六个字节?

我正在使用带有 32 位 GCC 编译器的 64 位机器。

【问题讨论】:

  • 你从哪里得到“堆栈指针地址有六个字节”?我看到应该是 4 个字节的增量(32 位地址,4 字节对齐)。
  • 只要 MMU 正确处理,堆栈指针可以指向 o/s​​ 喜欢的任何位置。您显示的值类似于我在带有 64 位编译器的 Mac OS X 上看到的值——它设计了不同的系统。您看到的不是使用 32 位编译器的结果。有大量的空闲内存空间;由系统决定如何使用它。堆栈指针有 8 个字节,与其他所有指针相同。最重要的一对字节只是零,因此printf() 不添加四个前导零。
  • 旁注:您所要求的都不是由 C 语言标准规定的(而是取决于编译器实现)。
  • 堆栈的其余部分可能是台式计算机需要用于可执行文件的启动代码的杂项?不过似乎太过分了,肯定连 Linux 都不会那么糟糕?
  • @JonathanLeffler 即使 CPU 可以支持 64 个地址范围,32 位程序的虚拟地址也是 32 位的。没有前导零,地址实际上是 32 位宽。

标签: c pointers memory-management stack


【解决方案1】:

栈指针在最后一个地址(0x7fffffffffff)

如果运行 32 位进程,在进行 通常 2GB/2GB 拆分时,最后一个地址(减去保留区域)将是 0x7fff ffff。但 32 位 Linux 通常只保留 1GB 内核地址区域,因此堆栈实际上会从 0xc000 0000 (TASK_SIZE) 开始。

您在这里看到的是 x64 地址空间布局的奇怪拆分。在这里,确实用户地址空间以0x0000 7fff ffff ffff 结束,0xffff ff80 0000 0000 及以上为内核保留。

当前的 MMU 实际上强制执行此操作,0xfffe ff80 0000 0000 或类似地址不是有效地址,位 47-63 必须等于形成 Canonical Form Address

为什么栈指针地址有六个字节?

从你的程序的输出来看,它看起来不像。 您正在打印每个变量在堆栈上的大小,而不是指针大小。来自printf() 的六字节地址实际上是 64 位地址,前导零被切断(感谢@Jonathan Leffler 发现了这一点)。

确实是sizeof(int *) == 8,但sizeof(int) == 4,因为即使是64位Linux也有32bit ints(只有long是64位)。不过,很容易错过这一点。

为什么变量没有存储在堆栈帧的最后一部分(0x7fffffffffff),它跳过了一些内存位置并被存储?这种行为有什么正当理由吗?

如果您查看here,在用户堆栈开始之前,有相当多的东西进入该地址空间。由于您可能需要整个页面来保护此代码,因此可能会有一些开销。添加库启动代码,您可能会获得相当多字节的内存。

那里的大部分代码可能是从父进程继承的写时复制(甚至是只读的)。

编辑:在 x64 上,任何内核导出的代码都可能进入更高的内存区域。不要声称我没有验证的东西;-)。

附带说明:当我在 64 位 FreeBSD 10.2 上编译和运行您的代码时,我得到了这个

0x7fffffffe5c8     0x7fffffffe5c4    0x7fffffffe5c0
c --> 0x7fffffffe5c0
b --> 0x7fffffffe5c4
a --> 0x7fffffffe5c8

这与您的输出相似,尽管 FreeBSD 似乎以不同的方式定位堆栈。

在 32 位模式下运行,我明白了:

0xffffd788     0xffffd784    0xffffd780
c --> 0xffffd780
b --> 0xffffd784
a --> 0xffffd788

解释后者可能真的很有趣(例如,我的内核地址空间在哪里?)。

【讨论】:

    【解决方案2】:
    • 变量可以存储在任何地址
    • 该地址包含超过 32 位,因此它分配了 64 位 地址

    【讨论】:

    • 栈帧中变量按降序存储
    • @SathishRam:这是错误的。编译器将优化并通常将给定的调用帧槽用于多种目的(具有相同地址的几个变量,因为它们从不同时使用),并且一些变量仅保存在寄存器中(不会溢出到调用中)框架)
    猜你喜欢
    • 2014-12-05
    • 1970-01-01
    • 1970-01-01
    • 2015-09-22
    • 2017-09-24
    • 2010-11-13
    • 2014-05-13
    • 1970-01-01
    • 2020-11-26
    相关资源
    最近更新 更多