【问题标题】:C memory management in gccgcc 中的 C 内存管理
【发布时间】:2013-04-13 20:28:35
【问题描述】:

我在 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

现在请看一下这段代码sn-p:

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

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

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

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

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

现在请看一下这段代码sn-p:

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) 为什么要浪费时间先对变量进行排序然后分配内存,而不是按照“先到先得”的原则直接分配内存?这种排序然后分配内存有什么特别的好处吗?

现在请看一下这段代码sn-p:

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;
}

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

所以这是我的问题:

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

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

【问题讨论】:

  • 没有真正涉及“标准”。它可能取决于所使用的编译器的版本、ABI、内核、运行该程序的环境等。ABI 可能要求堆栈指针是 16 字节对齐的,并且编译器将变量放入“as它希望”。 array2 必须是字对齐的(4 字节的倍数),而 array1 不需要...
  • 为什么走廊这边的房间号都是偶数?为什么走廊对面的房间号都是奇数
  • 你到底为什么要问?你的问题背后的原因是什么,你为什么真的在乎? (您不应该编写严重依赖其确切原因的程序)。
  • @BasileStarynkevitch - 如果编译器落后于以上 3 个观察结果,那么它这样做肯定是有原因的。我的意思是仔细排序变量然后存储它们或总是以某个以0结尾的随机十六进制值存储数组真的不是一件随机的事情。必须有一些逻辑。为什么编译器要承受这么多的痛苦?如果您知道,请提及任何具体的好处。
  • @rootkea:我还是不明白你为什么要问这个问题,为什么这对你很重要。这通常是您不关心的细节的一部分。相信编译器和系统,它应该在实践中非常明智地做事。

标签: c arrays variables gcc memory-management


【解决方案1】:

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

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

【讨论】:

  • 所以 gcc 是上述所有 3 个观察结果的背后。好的。所以这回答了问题 1)、3) 和 5)。编译器会进行这种分配以减少获取时间。但这是安静的一般性声明。 1)我的意思是,如果变量按其大小的递增顺序排序然后分配内存,它究竟对编译器有什么帮助? 2)如果变量的存储方式使得下一个可用内存位置是某个以0结尾的随机十六进制值,它如何减少获取时间或它应该做的任何好处?
  • @rootkea - 如果地址是数据大小的偶数倍,那么在您的处理器 (x86) 上获取数据是最快的。这样,信息可以直接在硬件中传播,而无需任何可能的延迟来整理比特。例如,如果您的double 位于以 4 结尾的地址上,则处理器可能必须读取两个 64 位字并从每次读取中选择四个字节。这可能需要一些额外的时间。
  • C 标准没有规定自动变量分配在堆栈上。在大多数情况下,它们是,但它们不需要。
【解决方案2】:

部分原因是您的系统和处理器的 application binary interface (ABI) 规范规定的。

查看x86 calling conventionsSVR4 x86-64 ABI supplement(我提供的是最近副本的 URL;最新的原件在网上很难找到)。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-09-06
    • 2018-12-17
    • 2011-06-28
    • 1970-01-01
    相关资源
    最近更新 更多