为什么堆栈和堆都增长?

时间:2017-09-04 13:19:40

标签: c memory

我写了这样的代码

int a = 0;
int b = 0;
int *m = (int *)malloc(2* sizeof(int));

printf("%x, %x\n", &a, &b);
printf("%x, %x", &m[0], &m[1]);

得到结果:

46372e18, 46372e1c
d062ec20, d062ec24

堆栈是否会长大并堆积起来?

5 个答案:

答案 0 :(得分:8)

要了解“ 1 ”堆栈“ 2 的增长方式,您必须至少进行一次函数调用。像这样的东西,例如:

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>

static ptrdiff_t
stack_probe(uintptr_t stack_addr_from_main)
{
    int var;
    uintptr_t stack_addr_from_me = (uintptr_t)&var;

    return ((intptr_t) stack_addr_from_me) - 
           ((intptr_t) stack_addr_from_main);
}

int
main(void)
{
    int var;
    uintptr_t stack_addr_from_main = (uintptr_t)&var;
    ptrdiff_t stack_delta = stack_probe(stack_addr_from_main);
    printf("Stack offset from one function call = %td\n", stack_delta);
    return 0;
}

你必须这样做,因为大多数编译器在进入时,在所谓的“堆栈帧”中为函数调用全部分配所有堆栈空间,并在其中组织空间他们认为合适。因此,比较同一函数的两个局部变量的地址并不能告诉你任何有用的东西。您还必须注意编译此程序并关闭“内联”;如果允许编译器将stack_probe合并到main中,则它将再次成为一个堆栈帧,结果将毫无意义。 (有些编译器允许你逐个函数地控制内联,但据我所知,没有标准的方法可以做到这一点。)

此程序打印的数字是“未指定”的C标准 3 (这意味着“它将打印某些号码,但标准不要求它是任何特定的数字“)。但是,几乎所有计算机上你都可能会在今天开始使用它,它会打印一个负数,这意味着堆栈会向下增长。如果您设法在运行HP-UX的PA-RISC计算机上运行它(它可能甚至不能编译,不幸的是;我不记得HP-UX是否曾经拥有符合C99标准的库)它将打印一个正数,并且这意味着堆栈向上增长。

计算机,其上由此程序打印的数字并不意味着什么,因为它们相当于“堆栈”不一定是连续的内存块。查找最简单版本的“拆分堆栈”。

顺便说一下,“堆”不一定会长大。连续调用malloc返回指针,彼此没有任何有意义的关系,总是。

1 可以有多个堆栈,例如在使用线程时。

2 有趣的事实:C标准中的“堆栈”一词出现无处。需要支持递归函数调用,但实现如何管理完全由实现完成。

3 此外,该程序是否将编译是实现定义的,因为不需要实现intptr_tuintptr_t。但是,如果我没有使用这些类型,该程序将具有未定义的行为(“它允许执行任何操作,包括崩溃并删除所有代码”),因为您只允许当它们指向相同的数组时,取两个指针的差异,这些不是。

答案 1 :(得分:3)

标准未规定所有这些。该标准甚至没有提到堆栈和堆。这两个都是标准不需要的实现细节。

此外,您只有一个动态对象(malloc'ed对象),它将遵循数组的正常布局。因此,您无法说明堆在系统上的增长情况。如果你想试试看你的系统做了什么,你至少需要两个malloc'ed对象。

要打印指针,请使用:

printf("%p, %p\n", (void*)&a, (void*)&b);

答案 2 :(得分:1)

因为指针算术:

printf("%x, %x", &m[0], &m[1]);

(请注意,打印指针值需要%p格式,其他格式可以“正常”但它们也可能会中断)

m[1]的地址是m[0]sizeof(int)的地址,不依赖于m的分配位置(全局或自动)

在这里:

int a = 0;
int b = 0;

与结构成员相反,编译器可以在相对于彼此选择的任何地方找到自动变量。它可以交换它们,对具有较低对齐约束的那些进行分组等等......所以你在这里看一个实现细节。

答案 3 :(得分:0)

是的,几乎所有现代系统的堆栈都在增长。但是,1。这是一个实现细节,2是堆栈帧,而不是单个变量。

当你的函数被调用时,它会在堆栈上创建一个堆栈帧,它基本上是堆栈内存的一个区域,用于保存局部变量。每个函数都放在这个堆栈框架中,完全取决于编译器。

但是,如果您运行此代码:

void foo() {
    int b;
    printf("%p\n", (void*)&b);
}

int main() {
    int a;
    printf("%p\n", (void*)&a);
    foo();
}

您会发现b的地址小于a的地址:当foo()被调用时,它会创建一个新的堆栈帧,b将会在这个堆栈框架内,它将在main()分配的堆栈框架下面。

对于堆,没有任何方向的概念。当然,大多数分配器都会在分配内存方面表现出一些模式,但是当它们都重用free()时的内存时,它们都会在某些时候打破它们的模式。

答案 4 :(得分:0)

正如许多人所指出的那样,C搁浅并没有具体说明这一点。

但是我想触及不同的方面,测试你做的是不足以确定堆正在成长 - 单词还是单词。

如下所示更新代码并检查结果;

#include <stdio.h>
#include <stdlib.h>


void test()
{
    int a = 0;
    int b = 0;
    printf("4:%p, %p\n", (void *)&a,(void *)&b);
}

void  main() {
    int a = 0;
    int b = 0;
    int *m = malloc(2* sizeof(int));
    int *n = malloc(2* sizeof(int));

    printf("1:%p, %p\n",(void *)&a,(void *)&b);
    printf("2:%p, %p\n",(void *)&m[0],(void *)&m[1]);
    printf("3:%p, %p\n",(void *)&n[0],(void *)&n[1]);

    test();
}

在我的系统上,我可以看到堆地址&#34;增加&#34;和堆栈地址&#34;减少&#34;

在您的代码中,您没有任何函数调用,因此堆栈帧只有一个。