【问题标题】:C++ struct memory overhead in clang?C++ struct内存开销在clang中?
【发布时间】:2015-02-12 13:22:02
【问题描述】:

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

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

*(&a - 2) = -1234;

这稍后将打印-1234,而不是*(&amp;a - 2)上的垃圾值,表明clang在创建Obj obj后没有写入这个值,这让我想知道为什么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 上运行。谁能解释一下clang浪费4字节内存是否有更大的目的?

【问题讨论】:

  • 你打印出实际的内存地址了吗?我的猜测是 8Byte 对齐允许更有效的参数传递或防止结构被拆分为两个缓存行(64 字节对齐)。至于与 g++ 的比较:两者都在 8 字节边界对齐结构,但在未优化模式下,clang 编译器更严格地遵循您的数据布局。正如你所做的是(据我所知)UB,这两种实现都是正确的,如果你不打开优化,也没有理由“抱怨”浪费。

标签: c++ pointers struct stack overhead


【解决方案1】:

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

此外,结构和类布局由 GCC 和 Clang 通用的标准定义,因此我向您保证它们在 Linux 上是相同的(模数错误)。

本质上,您假设编译器在堆栈上以确切的顺序排列您的局部变量,没有其他任何东西,但绝对没有理由相信这是真的。编译器没有义务以任何方式布置堆栈。

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

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

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

      编译器也可以在最后一个成员之后填充。大多数编译器会填充这样 sizeof(T) % sizeof(largest member of T) == 0,像这样:

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

      对于堆栈变量(您的 a、b、obj 和 c),编译器可以随意重新排序和填充。

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

      【讨论】:

        【解决方案4】:

        当然有对齐。在 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,整数也是 32 位的,而对齐可能是 64 位或 8 个字节。

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

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

        【讨论】:

        • 这是完全错误的。 OP 观察到的行为与结构填充或对齐无关。
        • 包含两个 ints 的 struct 在我听说过的任何架构上都不需要填充。
        • @Puppy:您的评论有 50% 不正确。观察到的行为是由于对齐造成的。
        • @TonyK:观察到的行为是由于编译器在调试模式下运行而没有在意如何充分利用堆栈空间。
        • @Puppy:在英特尔 IA-64 芯片的 64 位模式下,Clang 和 GCC 在 8 字节边界上对齐所有结构。无论调试级别如何。
        猜你喜欢
        • 2013-03-22
        • 2010-12-31
        • 2010-12-10
        • 1970-01-01
        • 2021-08-27
        • 2011-11-16
        • 2011-02-12
        • 2020-12-13
        • 1970-01-01
        相关资源
        最近更新 更多