clang中的C ++ struct内存开销?

时间:2014-12-14 07:23:13

标签: c++ pointers struct stack overhead

clang中是否存在每个结构的内存开销?通常,结构的大小只是其所有成员的总大小。但在clang中似乎并非如此:

#include <iostream>
using namespace std;

struct Obj {
    int x;
    int y;
};

int main() {
    int a = 42;
    int b = 43;
    Obj obj = {100, 101};
    int c = 44;

    cout << *(&a - 0) << endl;  // 42
    cout << *(&a - 1) << endl;  // 43
    cout << *(&a - 2) << endl;  // On clang this prints garbage value instead of 101, how strange...
    cout << *(&a - 3) << endl;  // 101
    cout << *(&a - 4) << endl;  // 100
    cout << *(&a - 5) << endl;  // 44

    return 0;
}

我用clang++ program.cpp编译,没有优化。版本:

Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
Target: x86_64-pc-linux-gnu
Thread model: posix

似乎根本没有使用(&a - 2)处的内存位置。我在int aint b声明中插入了以下代码:

*(&a - 2) = -1234;

这将稍后打印-1234而不是*(&a - 2)上的垃圾值,表示在创建Obj obj后clang没有写入此值,这让我想知道为什么clang保留它首先。

在gcc上,这种行为有所不同。首先,堆栈似乎在我的机器上的gcc上向更高的内存地址增长。此外,gcc自行将变量c放在b之后,所以它看起来像这样:

cout << *(&a + 0) << endl;  // 42
cout << *(&a + 1) << endl;  // 43
cout << *(&a + 2) << endl;  // 44 gcc moved c here even without optimization
cout << *(&a + 3) << endl;  // 100
cout << *(&a + 4) << endl;  // 101

g++ program.cpp一起编译。版本:

 g++ (Ubuntu 4.8.2-19ubuntu1) 4.8.2

当我将程序移植到C时,行为相同。一切都在64位Linux Mint 17.1上运行。有人可以解释是否有更大的目的来谴责浪费4个字节的内存?

4 个答案:

答案 0 :(得分:2)

您运行的测试与结构无关,也与堆栈无关。从随机堆栈内存位置开始读取是未定义的行为,编译器有权利用它们做任何想做的事情。没有优化,很明显编译器不会费心地使用它们。没有优化,没有人关心丢失的几个字节。

此外,struct和class layout是由GCC和Clang共同的标准定义的,所以我向你保证它们在Linux上是相同的(模数错误)。

基本上,你假设编译器正在堆栈中按照确切的顺序布置你的局部变量而没有别的东西,但绝对没有理由相信这是真的。编译器没有义务以任何方式布局堆栈。

答案 1 :(得分:1)

Clang只是在8字节边界上对齐结构,因此可以更有效地复制它。 GCC也这样做,但GCC的堆栈布局不同,所以结构已经对齐。

答案 2 :(得分:1)

以下是编译器允许和不允许的内容:编译器允许在第一个成员之前填充。编译器允许重新排序成员(尽管gcc / clang支持一些明确允许的命令行标志)。

允许编译器在任何成员之间填充,并且大多数将填充以使所有成员自然对齐(&amp; member%sizeof(member)== 0):

struct foo {
    char a;
    int b; // pad 3 bytes before b
}

编译器也允许在最后一个成员之后填充。大多数编译器将填充sizeof(T)%sizeof(T的最大成员)== 0,如下所示:

struct foo {
    int a;
    char b; // pad 3 bytes after b
}

对于堆栈变量(你的a,b,obj和c),编译器可以自由重新排序并按照它想要的方式填充。

成员类型的大小以及是否利用打包取决于体系结构,编译器版本和编译器标志,因此人们运行代码的结果不同。

答案 3 :(得分:-1)

当然,有一些对齐方式。它在32位程序上是4个字节,我不知道64位。

实际上有一个#pragma

#pragma pack(push)  /* push current alignment to stack */
#pragma pack(1)     /* set alignment to 1 byte boundary */

struct MyPackedData
{
    char Data1;
    long Data2;
    char Data3;
};

#pragma pack(pop)   /* restore original alignment from stack */

这样,当你有一个结构数组时,访问它们不会不断地生成需要处理的不对齐异常。见What's the actual effect of successful unaligned accesses on x86?

有关详细信息,请参阅https://en.wikipedia.org/wiki/Data_structure_alignment

我不知道你是否就是这种情况,因为无论如何都应该对齐2个整数,但是它们可能不在64位架构上?对于64位CPU,Int甚至是32位,而对齐可能是64位或8字节。

当然,由于结构在堆栈中,其他变量及其对齐也很重要。

另见struct alignment C/C++Memory alignment in C-structs