堆栈是向上还是向下增长?

82 c memory stack

我在c中有这段代码:

int q = 10;
int s = 5;
int a[3];

printf("Address of a: %d\n",    (int)a);
printf("Address of a[1]: %d\n", (int)&a[1]);
printf("Address of a[2]: %d\n", (int)&a[2]);
printf("Address of q: %d\n",    (int)&q);
printf("Address of s: %d\n",    (int)&s);
Run Code Online (Sandbox Code Playgroud)

输出是:

Address of a: 2293584
Address of a[1]: 2293588
Address of a[2]: 2293592
Address of q: 2293612
Address of s: 2293608
Run Code Online (Sandbox Code Playgroud)

所以,我看到,从那里a开始a[2],内存地址每个增加4个字节.但是,从qs,内存地址减少了4个字节.

我想知道两件事:

  1. 堆栈是成长还是下降?(在这种情况下看起来对我来说都是)
  2. a[2]q内存地址之间发生了什么?为什么那里存在很大的记忆差异?(20个字节).

注意:这不是作业问题.我很好奇堆栈是如何工作的.谢谢你的帮助.

Gan*_*ian 70

堆栈(成长或增长)的行为取决于应用程序二进制接口(ABI)以及如何组织调用堆栈(也称为激活记录).

在其整个生命周期中,程序必然会与其他程序(如OS)进行通信.ABI确定程序如何与另一个程序通信.

不同体系结构的堆栈可以以任何一种方式增长,但对于体系结构,它将是一致的.请检查维基链接.但是,堆栈的增长是由该架构的ABI决定的.

例如,如果您使用MIPS ABI,则调用堆栈定义如下.

让我们考虑函数'fn1'调用'fn2'.现在'fn2'看到的堆栈帧如下:

direction of     |                                 |
  growth of      +---------------------------------+ 
   stack         | Parameters passed by fn1(caller)|
from higher addr.|                                 |
to lower addr.   | Direction of growth is opposite |
      |          |   to direction of stack growth  |
      |          +---------------------------------+ <-- SP on entry to fn2
      |          | Return address from fn2(callee) | 
      V          +---------------------------------+ 
                 | Callee saved registers being    | 
                 |   used in the callee function   | 
                 +---------------------------------+
                 | Local variables of fn2          |
                 |(Direction of growth of frame is |
                 | same as direction of growth of  |
                 |            stack)               |
                 +---------------------------------+ 
                 | Arguments to functions called   |
                 | by fn2                          |
                 +---------------------------------+ <- Current SP after stack 
                                                        frame is allocated
Run Code Online (Sandbox Code Playgroud)

现在你可以看到堆栈向下增长.因此,如果变量被分配给函数的本地帧,则变量的地址实际上向下增长.编译器可以决定内存分配的变量顺序.(在你的情况下,它可以是'q'或's',它是第一次分配的堆栈内存.但是,通常编译器会根据变量声明的顺序堆栈内存分配).

但是在数组的情况下,分配只有单个指针,而需要分配的内存实际上是由单个指针指向的.内存需要与数组连续.因此,尽管堆栈向下增长,但对于数组,堆栈会增长.

  • 此外,如果要检查堆栈是向上还是向下增长.在main函数中声明一个局部变量.打印变量的地址.从main调用另一个函数.在函数中声明一个局部变量.打印其地址.根据打印的地址,我们可以说堆栈增长或减少. (4认同)

Cra*_*rks 44

这实际上是两个问题.一个是关于当一个函数调用另一个函数时(当分配新帧时)堆栈增长的方式,另一个是关于如何在特定函数的框架中布置变量.

C标准都没有规定,但答案有点不同:

  • 当分配新帧时,堆栈会以哪种方式增长 - 如果函数f()调用函数g(),那么f帧指针是否大于或小于g帧的指针? 这可以采用任何一种方式 - 它取决于特定的编译器和体系结构(查找"调用约定"),但它在给定平台内始终保持一致(有一些奇怪的例外,请参阅注释).向下是更常见的; 在x86,PowerPC,MIPS,SPARC,EE和Cell SPU中就是这种情况.
  • 函数的局部变量如何在其堆栈框架内布局?这是未指明的,完全不可预测; 编译器可以自由安排其局部变量,但是它喜欢获得最有效的结果.

  • "它在给定平台内始终保持一致" - 不能保证.我见过一个没有虚拟内存的平台,其中堆栈是动态扩展的.新的堆栈块实际上是malloced,这意味着你将"向下"一个堆栈块一段时间,然后突然"侧向"到另一个块."侧身"可能意味着更大或更小的地址,完全取决于平局的运气. (6认同)
  • 有关第2项的其他详细信息 - 编译器可能能够确定变量永远不需要在内存中(在变量的生命周期内将其保存在寄存器中),和/或如果两个或多个变量的生命周期不存在t重叠,编译器可能决定对多个变量使用相同的内存. (2认同)
  • 我认为S/390(IBM zSeries)有一个ABI,其中调用帧被链接而不是在堆栈上增长. (2认同)
  • 在S/390上更正.呼叫是"BALR",分支和链接寄存器.返回值被放入寄存器而不是压入堆栈.返回函数是该寄存器内容的分支.随着堆栈变得更深,空间被分配在堆中并且它们被链接在一起.这就是MVS等同于"/ bin/true"的名称:"IEFBR14".第一个版本有一条指令:"BR 14",它分支到包含返回地址的寄存器14的内容. (2认同)

R S*_*hko 13

堆栈增长的方向是体系结构特定的.也就是说,我的理解是只有极少数硬件架构具有成长的堆栈.

堆栈增长的方向与单个对象的布局无关.因此,当堆栈可能长大时,数组将不会(即&array [n]将始终为<&array [n + 1]);


pax*_*blo 6

标准中根本没有规定如何在堆栈上组织事物。事实上,您可以构建一个完全不将数组元素存储在堆栈上的连续元素处的一致编译器,只要它有能力正确执行数组元素算术(以便它知道,例如,1是距离 a[0] 1K,可以对此进行调整)。

您可能得到不同结果的原因是,虽然堆栈可能会向下增长以向其中添加“对象”,但数组是单个“对象”,并且它可能具有按相反顺序升序的数组元素。但依赖这种行为并不安全,因为方向可能会改变,变量可能会因多种原因而交换,包括但不限于:

  • 优化。
  • 结盟。
  • 编译器的堆栈管理部分的人的突发奇想。

请参阅此处我关于堆栈方向的精彩论文:-)

回答您的具体问题:

  1. 堆栈是向上增长还是向下增长?
    这根本不重要(就标准而言),但是,既然您问了,它可以在内存中增加减少,具体取决于实现。
  2. a[2] 和 q 内存地址之间会发生什么?为什么内存差异很大?(20 字节)?
    这根本不重要(就标准而言)。请参阅上文了解可能的原因。