我在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
因此,我看到从a
到a[2]
,内存地址各增加4个字节。
但是从q
到s
,内存地址减少了4个字节。
我想知道两件事:
a[2]
和q
内存地址之间发生了什么?为什么那里存在很大的记忆差异? (20字节)。注意:这不是作业问题。我很好奇堆栈是如何工作的。谢谢你的帮助。
答案 0 :(得分:67)
堆栈(成长或增长)的行为取决于应用程序二进制接口(ABI)以及如何组织调用堆栈(也称为激活记录)。
在整个生命周期中,程序必然会与其他程序(如OS)进行通信。 ABI确定程序如何与另一个程序通信。
不同体系结构的堆栈可以以任何一种方式增长,但对于体系结构,它将是一致的。请查看this wiki链接。但是,堆栈的增长是由该架构的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
现在你可以看到堆栈向下增长。因此,如果变量被分配给函数的本地帧,则变量的地址实际上向下增长。编译器可以决定内存分配的变量顺序。 (在你的情况下,它可以是'q'或's',它是第一个分配的堆栈内存。但是,通常编译器会根据变量声明的顺序堆栈内存分配。)
但是在数组的情况下,分配只有单个指针,而需要分配的内存实际上是由单个指针指向的。内存需要与数组连续。因此,尽管堆栈向下增长,但对于数组,堆栈会增长。
答案 1 :(得分:43)
这实际上是两个问题。一个是关于哪个方式the stack grows when one function calls another(当分配新帧时),另一个是关于如何在特定函数的帧中布置变量。
C标准都没有规定,但答案有点不同:
f
的帧指针将大于或小于{{1}框架指针?这可以是两种方式 - 它取决于特定的编译器和架构(查找“调用约定”),但它在给定平台内始终是一致的 (有一些奇怪的例外,请参阅评论)。向下是更常见的;在x86,PowerPC,MIPS,SPARC,EE和Cell SPU中就是这种情况。答案 2 :(得分:13)
堆栈增长的方向是特定于体系结构的。也就是说,我的理解是只有极少数硬件架构具有成长的堆栈。
堆栈增长的方向与单个对象的布局无关。因此,当堆栈可能长大时,阵列将不会(即&amp; array [n]将始终&lt;&amp; array [n + 1]);
答案 3 :(得分:4)
标准中没有任何内容可以规定如何在堆栈上组织事物。事实上,你可以构建一个符合标准的编译器,它根本没有将数组元素存储在堆栈上的连续元素上,前提是它有智能仍然可以正确地进行数组元素运算(例如,它知道一个{{ 3}}距离[0] 1K,并且可以进行调整。
你可能得到不同结果的原因是,虽然堆栈可能会向下增长以向其添加“对象”,但是数组是单个“对象”,并且它可能具有相反顺序的升序数组元素。但是依靠这种行为是不安全的,因为方向可以改变,变量可以因各种原因而交换,包括但不限于:
有关堆叠方向的优秀论文,请参阅1: - )
回答您的具体问题:
答案 4 :(得分:2)
在x86上,堆栈帧的内存“分配”只是从堆栈指针中减去必要的字节数(我相信其他架构是相似的)。从这个意义上说,我猜堆栈会“向下”增长,因为当你更深入地调用堆栈时,地址会逐渐变小(但是我总是设想内存从左上角的0开始,并在移动时获得更大的地址向右和向下包裹,所以在我的心理形象中,堆栈长大......)。声明的变量的顺序可能与它们的地址没有任何关系 - 我相信标准允许编译器重新排序它们,只要它不会引起副作用(如果我错了,有人请纠正我) 。它们只是在从堆栈指针中减去字节数时创建的已使用地址中的某个位置。
阵列周围的间隙可能是某种填充物,但这对我来说很神秘。
答案 5 :(得分:1)
编译器可以在本地堆栈帧的任何位置自由分配本地(自动)变量,您无法从中纯粹地推断堆栈增长方向。您可以通过比较嵌套堆栈帧的地址来推断堆栈增长方向,即比较函数堆栈框架内局部变量的地址与其被调用者的比较:
#include <stdio.h>
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;
}
答案 6 :(得分:1)
首先,它在内存中有8个字节的未使用空间(它不是12,记住堆栈向下增长,所以未分配的空间是从604到597)。为什么? 因为每个数据类型从可被其大小整除的地址开始占用内存空间。在我们的情况下,3个整数的数组需要12个字节的内存空间,而604不能被12整除。所以它会留下空格,直到它遇到一个可被12整除的内存地址,它是596.
因此,分配给数组的内存空间从596到584.但由于数组分配是连续的,所以数组的第一个元素从584地址开始,而不是从596开始。
答案 7 :(得分:1)
向下增长,这是因为当涉及内存中的数据集时,小端字节顺序标准。
你可以看到它的一种方法是,如果从顶部看0,从底部看最大值,堆栈会向上增长。
堆栈向下增长的原因是能够从堆栈或基指针的角度取消引用。
请记住,任何类型的解除引用都会从最低到最高地址增加。由于堆栈向下增长(从最高到最低地址),因此可以将堆栈视为动态内存。
这就是为什么这么多编程和脚本语言使用基于堆栈的虚拟机而不是基于寄存器的原因之一。
答案 8 :(得分:0)
我不认为这是确定性的。 a数组似乎“增长”,因为该内存应该连续分配。但是,由于q和s完全不相关,编译器只是将它们中的每一个都放在堆栈中的任意空闲内存位置,可能是最适合整数大小的位置。
在[2]和q之间发生的事情是q的位置周围的空间不够大(即,不大于12个字节)来分配3个整数数组。
答案 9 :(得分:0)
我的堆栈似乎延伸到编号较低的地址。
如果我使用不同的编译器调用,它可能在另一台计算机上,甚至在我自己的计算机上可能不同。 ...或者编译器muigt选择不使用堆栈(内联所有内容(函数和变量,如果我没有获取它们的地址))。
$ 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 level 4: x is at 0x7fff7781190c level 3: x is at 0x7fff778118ec level 2: x is at 0x7fff778118cc level 1: x is at 0x7fff778118ac level 0: x is at 0x7fff7781188c
答案 10 :(得分:0)
堆栈增长(在x86上)。但是,当函数加载时,堆栈被分配在一个块中,并且您无法保证项目在堆栈中的顺序。
在这种情况下,它在堆栈上为两个int和一个三int数组分配了空间。它还在数组后再分配了12个字节,所以它看起来像这样:
a [12字节]
填充(?)[12字节]
s [4字节]
q [4字节]
无论出于何种原因,您的编译器决定需要为此函数分配32个字节,可能还需要更多。作为一名C程序员,这对你来说是不透明的,你不知道为什么。
如果您想知道原因,请将代码编译为汇编语言,我相信它是MS上C编译器的gcc和/ S上的-S。如果查看该函数的开始指令,您将看到保存旧堆栈指针,然后从中减去32(或其他东西!)。从那里,您可以看到代码如何访问该32字节的内存块并找出您的编译器正在做什么。在函数结束时,您可以看到正在恢复的堆栈指针。
答案 11 :(得分:0)
这取决于您的操作系统和编译器。
答案 12 :(得分:0)
Stack确实在成长。所以f(g(h())),为h分配的堆栈将从较低的地址开始,然后g和g将低于f&#39; s。但是堆栈中的变量必须遵循C规范,
http://c0x.coding-guidelines.com/6.5.8.html
1206如果指向的对象是同一聚合对象的成员,则指向稍后声明的结构成员的指针比指向结构中先前声明的成员的指针要大,指向具有较大下标值的数组元素的指针比指向元素的指针要大具有较低下标值的相同数组。
&amp; a [0]&lt; &amp; a [1],必须始终为真,无论如何&#39; a&#39;被分配
答案 13 :(得分:0)
这取决于体系结构。要检查您自己的系统,请使用GeeksForGeeks中的以下代码:
// C program to check whether stack grows
// downward or upward.
#include<stdio.h>
void fun(int *main_local_addr)
{
int fun_local;
if (main_local_addr < &fun_local)
printf("Stack grows upward\n");
else
printf("Stack grows downward\n");
}
int main()
{
// fun's local variable
int main_local;
fun(&main_local);
return 0;
}