6

我在 Ubuntu 12.10 x86_64 上使用 gcc 版本 4.7.2。

首先是我终端上数据类型的大小:

sizeof(char) = 1    

sizeof(short) = 2          sizeof(int) = 4
sizeof(long) = 8           sizeof(long long) = 8

sizeof(float) = 4          sizeof(double) = 8
sizeof(long double) = 16

现在请看一下这段代码片段:

int main(void)
{   
    char c = 'a';
    printf("&c = %p\n", &c);
    return 0;
}

如果我没记错的话,我们无法预测c. 但是每次这个程序都会给出一些以 . 结尾的随机十六进制地址f。所以下一个可用位置将是一些以 . 结尾的十六进制值0。在其他数据类型的情况下,我也观察到了这种模式。对于 int 值,地址是一些以 . 结尾的十六进制值c。对于双精度,它是一些随机的十六进制值8,以此类推。

所以我在这里有两个问题。

1) 谁在管理这种内存分配?它是 gcc 还是 C 标准?

2)不管是谁,为什么会这样?为什么变量以这样一种方式存储,即下一个可用内存位置从以 结尾的十六进制值开始0?有什么具体的好处吗?

现在请看一下这段代码片段:

int main(void)
{   
    double a = 10.2;
    int b = 20;
    char c = 30;
    short d = 40;

    printf("&a = %p\n", &a);
    printf("&b = %p\n", &b);
    printf("&c = %p\n", &c);
    printf("&d = %p\n", &d);

    return 0;
}

现在在这里我观察到的对我来说是全新的。我认为变量会按照它们声明的顺序存储。但不是!事实并非如此。这是随机运行之一的示例输出:

&a = 0x7fff8686a698
&b = 0x7fff8686a694
&c = 0x7fff8686a691
&d = 0x7fff8686a692

似乎变量按其大小的递增顺序排序,然后以相同的排序顺序存储,但保持观察 1。即最后一个变量(最大的变量)以这样一种方式存储,即下一个可用内存位置是以 .结尾的十六进制值0

以下是我的问题:

3)谁是幕后黑手?它是 gcc 还是 C 标准?

4)为什么要浪费时间先对变量进行排序然后分配内存,而不是在“先到先得”的基础上直接分配内存?这种排序然后分配内存有什么特别的好处吗?

现在请看一下这段代码片段:

int main(void)
{   
    char array1[] = {1, 2};
    int array2[] = {1, 2, 3};

    printf("&array1[0] = %p\n", &array1[0]);
    printf("&array1[1] = %p\n\n", &array1[1]);

    printf("&array2[0] = %p\n", &array2[0]);
    printf("&array2[1] = %p\n", &array2[1]);
    printf("&array2[2] = %p\n", &array2[2]);

    return 0;
}

现在这对我来说也很震惊。我观察到的是,如果数组总是存储在某个以“0”结尾的随机十六进制值中,elements of an array >= 2并且如果elements < 2 它在观察 1 之后获得内存位置。

所以这是我的问题:

5) 谁在这个以 thing 结尾的随机十六进制值存储数组的背后0?它是 gcc 还是 C 标准?

6)现在为什么要浪费内存?我的意思是array2可以在之后立即存储array1(因此array2内存位置会以 结尾2)。但不是将其array2存储在下一个十六进制值处,0从而在其间留下 14 个内存位置。有什么具体的好处吗?

4

2 回答 2

7

堆栈和堆开始的地址由操作系统提供给进程。其他一切都由编译器决定,使用编译时已知的偏移量。其中一些事情可能遵循您的目标架构中遵循的现有约定,而其中一些则不遵循。

C 标准没有规定任何关于堆栈框架内局部变量的顺序的内容(正如评论中所指出的,它甚至根本没有规定使用堆栈)。当涉及到结构时,该标准只需要定义顺序,即使那样,它也没有定义特定的偏移量,只是这些偏移量必须按递增顺序排列。通常,编译器尝试以这样一种方式对齐变量,即访问它们需要尽可能少的 CPU 指令 - 并且标准允许这样做,而不是强制它。

于 2013-04-20T15:12:06.727 回答
5

部分原因是系统和处理器的应用程序二进制接口(ABI) 规范规定的。

请参阅x86 调用约定SVR4 x86-64 ABI 补充(我提供的是最近副本的 URL;最新的原件在 Web 上很难找到)。

在给定的调用框架内,编译器可以将变量放置在任意堆栈槽中。它可能会尝试(在优化时)随意重组堆栈,例如通过减少对齐约束。你不应该担心这一点。

编译器尝试将局部变量以适当的对齐方式放在堆栈位置。请参阅 GCC 的alignof扩展。编译器将这些变量放在哪里并不重要,请参阅我的答案。(如果它对您的代码很重要,您真的应该将变量打包在一个通用的 localstruct中,因为每个编译器、版本和优化标志都可以做不同的事情;所以不要依赖于特定编译器的精确行为)。

于 2013-04-20T15:02:03.313 回答