堆栈是向上还是向下?

我有这段代码在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); 

输出是:

 Address of a: 2293584 Address of a[1]: 2293588 Address of a[2]: 2293592 Address of q: 2293612 Address of s: 2293608 

所以,我看到从aa[2] ,内存地址每个增加4个字节。 但是从qs ,内存地址减less了4个字节。

我不知道2件事情:

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

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

堆栈(成长或增长)的行为取决于应用程序二进制接口(ABI)以及调用堆栈(aka激活logging)是如何组织的。

在整个生命周期中,程序必然会与其他程序(如操作系统)进行通信。 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 

现在你可以看到堆栈向下增长。 所以,如果variables分配给函数的本地框架,variables的地址实际上是向下增长的。 编译器可以决定内存分配的variables顺序。 (在你的情况下,它可以是'q'或's'是第一个被分配的堆栈内存,但是,通常编译器会根据variables的声明顺序来堆栈内存分配。

但是在数组的情况下,分配只有一个指针,需要分配的内存实际上是由一个指针指向的。 内存需要连续的数组。 所以,虽然堆栈向下增长,但是对于数组,堆栈却在不断增长。

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

C标准也没有具体规定,但答案略有不同:

  • 当一个新的帧分配时,堆栈增长的方式是 – 如果函数f()调用函数g(), f的帧指针是大于还是小于g的帧指针? 这可以以任何一种方式 – 这取决于特定的编译器和体系结构(查找“调用约定”),但它在给定的平台内总是一致的 (有几个奇怪的例外,请参阅评论)。 向下是更常见的; x86,PowerPC,MIPS,SPARC,EE和Cell SPU就是这种情况。
  • 一个函数的局部variables是如何在其堆栈框架中布局的? 这是不明确的,完全不可预测的。 编译器可以自由地安排它的局部variables,但它喜欢得到最有效的结果。

堆栈增长的方向是架构特定的。 这就是说,我的理解是,只有很less的硬件架构有成长的堆栈。

堆栈增长的方向与单个对象的布局无关。 所以,虽然堆栈可能会增长下来,数组将不会(即arrays[n]总是<&array [n + 1]);

标准中没有任何事情要求在堆栈中组织事物。 实际上,你可以构build一个符合的编译器,它根本不会将数组元素存储在堆栈中的连续元素上,只要它具有智能就可以正确地进行数组元素的算术运算(例如,它知道1是距离[0] 1K,可以调整)。

你可能会得到不同的结果的原因是因为,虽然堆栈可能会增长下来添加“对象”,数组是一个单一的“对象”,它可能有相反的顺序上升数组元素。 但是依靠这种行为是不安全的,因为方向可能会改变,variables可能由于各种原因而交换,包括但不限于:

  • 优化。
  • 对准。
  • 编译器的堆栈pipe理部分的人的奇思妙想。

在这里看到我的优秀论文堆栈方向:-)

在回答你的具体问题:

  1. 堆栈是长大还是长大?
    根本不重要(按照标准),但是,因为你问了,它可以在内存中增长减less,取决于实施。
  2. 在[2]和q内存地址之间发生了什么? 为什么这里有很大的记忆差异? (20字节)?
    根本无关紧要(就标准而言)。 请参阅上面的可能原因。

在x86上,堆栈帧的内存“分配”仅仅是从堆栈指针中减去必要的字节数(我相信其他的结构是相似的)。 从这个意义上说,我认为栈越来越小,因为当你更深入地调用堆栈时,地址越来越小(但是我总是将内存设想为从左上angular的0开始,在移动时获得更大的地址向右转,然后包裹起来,所以在我的心理图像中,堆栈长大了…)。 被声明的variables的顺序可能与它们的地址没有任何关系 – 我相信这个标准允许编译器对它们进行重新sorting,只要它不会引起副作用(如果我错了,请纠正我) 。 当它从堆栈指针中减去字节数时,它们只是被卡在所使用地址的空隙中。

数组周围的空白可能是某种填充,但对我来说这很神秘。

首先,它的8字节内存中未使用的空间(不是12,记忆栈向下增长,所以没有分配的空间是从604到597)。 为什么? 因为每个数据types都占用大小可以分割的地址从内存中获取空间。 在我们的情况下,3个整数的数组需要12个字节的内存空间,604个不能被12整除。因此,它留下了空的空间,直到遇到一个可以被12整除的内存地址,它是596。

所以分配给数组的内存空间是从596到584.但是由于数组分配是连续的,所以数组的第一个元素从584地址开始,而不是从596开始。

向下增长,这是因为当涉及到内存中的数据集时,小端字节顺序标准。

有一种方法可以看到,如果从顶部的0和底部的最大值看内存,堆栈会向上增长。

堆栈向下扩展的原因是能够从堆栈或基址指针的angular度来取消引用。

请记住,任何types的解引用都会从最低地址增加到最高地址。 由于堆栈向下增长(从最高地址到最低地址),所以您可以像处理dynamic内存那样对待堆栈。

这是为什么如此多的编程和脚本语言使用基于堆栈的虚拟机而不是基于寄存器的原因之一。

我的堆栈似乎延伸到较低编号的地址。

如果我使用不同的编译器调用,它可能会在另一台计算机上,甚至在我自己的计算机上不同。 …或者编译器muigt根本不select使用堆栈(内联一切(函数和variables,如果我没有把它们的地址))。

 $ cat stack.c #include <stdio.h> int stack(int x) { printf("level %d: x is at %p\n", x, (void*)&x); if (x == 0) return 0; return stack(x - 1); } int main(void) { stack(4); return 0; } 
 $ / usr / bin / gcc -Wall -Wextra -std = c89 -pedantic stack.c
  $ ./a.out
等级4:x在0x7fff7781190c处
等级3:x在0x7fff778118ec处
等级2:x在0x7fff778118cc
等级1:x在0x7fff778118ac处
等级0:x在0x7fff7781188c

编译器可以自由地在本地堆栈帧的任何地方分配本地(自动)variables,因此无法可靠地推断堆栈的生长方向。 您可以通过比较嵌套堆栈帧的地址来推断堆栈增长的方向,即比较函数的堆栈帧中的局部variables与其被调用者的地址:

 int f(int *x) { int a; return x == NULL ? f(&a) : &a - x; } int main(void) { printf("stack grows %s!\n", f(NULL) < 0 ? "down" : "up"); return 0; } 

堆栈增长减less(在x86上)。 但是,当函数加载时,堆栈被分配到一个块中,并且不能保证项目在堆栈上的顺序。

在这种情况下,它为堆栈中的两个int和一个三int数组分配了空间。 它还在数组之后分配了另外的12个字节,所以它看起来像这样:

a [12个字节]
填充(?)[12个字节]
s [4个字节]
q [4个字节]

不pipe什么原因,你的编译器决定需要为这个函数分配32个字节,可能还有更多。 对于C程序员来说这是不透明的,你不知道为什么。

如果你想知道为什么,把代码编译成汇编语言,我相信它是在MS的C编译器上的gcc和/ S上。 如果你看看这个函数的开始指令,你会看到旧的堆栈指针被保存,然后从中减去32(或者别的东西!)。 从那里,你可以看到代码如何访问这个32字节的内存块,并找出你的编译器在做什么。 在函数结束时,您可以看到正在恢复的堆栈指针。

这取决于你的操作系统和你的编译器。

堆栈确实长大了。 所以f(g(h())),为h分配的堆栈将从较低地址开始,那么g和g将会低于f。 但是堆栈中的variables必须遵循C规范,

http://c0x.coding-guidelines.com/6.5.8.html

1206如果指向的对象是同一个聚合对象的成员,那么稍后声明的结构成员的指针将比指向结构中较早声明的成员的指针大,并且具有较大下标值的数组元素的指针比指向相同元素的指针具有较低下标值的数组。

&a [0] <&a [1]必须始终为真,不pipea如何分配

我不认为这是确定性的。 一个数组似乎“增长”,因为内存应该连续分配。 然而,由于q和s根本就没有关系,编译器只是把它们中的每一个都放在堆栈中的任意空闲的内存位置,可能是最好的整数大小。

在[2]和q之间发生的事情是q的位置周围的空间不够大(即不大于12字节)以分配3整数数组。