6

我以下面的代码为例。

#include <iostream>

struct class1
{

    uint8_t     a;
    uint8_t     b;
    uint16_t    c;
    uint32_t    d;
    uint32_t    e;

    uint32_t    f;
    uint32_t    g;
};

struct class2
{

    uint8_t     a;
    uint8_t     b;
    uint16_t    c;
    uint32_t    d;
    uint32_t    e;

    uint64_t    f;
};

int main(){
    std::cout << sizeof(class1) << std::endl;
    std::cout << sizeof(class2) << std::endl;
    std::cout << sizeof(uint64_t) << std::endl;
    std::cout << sizeof(uint32_t) << std::endl;
}

印刷

20
24
8
4

所以很容易看出一个 uint64_t 和两个 uint32_t 一样大,为什么类 2 会有 4 个额外的字节,如果它们是相同的,除了用两个 uint32_t 替换一个 uint64_t。

4

3 回答 3

5

正如所指出的,这是由于padding

为了防止这种情况,您可以使用

#pragma pack(1)

class ... {

};
#pragma pack(pop)

它告诉你的编译器不对齐到 8 个字节,而是对齐到一个字节。pop 命令将其关闭(这非常重要,因为如果您在标题中这样做并且有人包含您的标题,则可能会出现非常奇怪的错误)

于 2014-10-15T11:22:26.797 回答
0

为什么一个 uint64_t 在类中使用时需要比 2 个 uint32_t 更多的内存?

原因是由于对齐要求的填充。

在大多数 64 位架构上,uint8_t 的对齐要求为 1,uint16_t 的对齐要求为 2,uint32_t 的对齐要求为 4,uint64_t 的对齐要求为 8。编译器必须确保结构中的所有成员都正确对齐并且结构的大小是其整体对齐要求的倍数。此外,编译器不允许重新排序成员。

所以你的结构最终布局如下

struct class1
{
    
    uint8_t     a; //offset 0
    uint8_t     b; //offset 1
    uint16_t    c; //offset 2
    uint32_t    d; //offset 4
    uint32_t    e; //offset 8
    
    uint32_t    f; //offset 12
    uint32_t    g; //offset 16
}; //overall alignment requirement 4, overall size 20.

struct class2
{
    
    uint8_t     a; //offset 0
    uint8_t     b; //offset 1
    uint16_t    c; //offset 2
    uint32_t    d; //offset 4
    uint32_t    e; //offset 8
    // 4 bytes of padding because f has an alignment requirement of 8
    uint64_t    f; //offset 16
}; //overall alignment requirement 8, overall size 24

以及如何防止这种情况?

不幸的是,没有好的通用解决方案。

有时可以通过重新排序字段来减少填充量,但这对您的情况没有帮助。它只是在结构中移动填充。具有需要 8 字节对齐的字段的结构的大小始终是 8 的倍数。因此,无论您如何重新排列字段,您的结构始终具有至少 24 的大小。

您可以使用编译器特定的功能,例如#pragma pack__attribute((packed))强制编译器将结构打包得比正常对齐要求所允许的更紧密。但是,除了限制可移植性之外,这在获取成员的地址或绑定对成员的引用时会产生问题。生成的指针或引用可能不满足对齐要求,因此使用起来可能不安全。

不同的编译器处理这个问题的方式不同。来自一些在上帝螺栓上玩耍的人。

  • g++ 9 到 11 将拒绝绑定对打包成员的引用,并在获取地址时发出警告。
  • clang 4 到 11 将在获取地址时发出警告,但会默默地绑定一个引用并将该引用传递到编译单元边界。
  • Clang 3.9 及更早版本将获取地址并静默绑定引用。
  • g++ 8 及更早版本和 clang 3.9 及更早版本(直到 Godbolt 上的最旧版本)也将拒绝绑定引用,但会在没有警告的情况下获取地址。
  • icc 将绑定一个指针或获取地址,而不会在任何一种情况下产生任何警告(尽管公平地说,英特尔处理器支持硬件中的未对齐访问)。
于 2021-01-13T20:11:17.113 回答
0

对齐规则(在 x86 和 x86_64 上)通常是对齐变量的 size

换句话说,32 位变量在 4 个字节上对齐,64 位变量在 8 个字节上对齐,等等。

的偏移量f是 12,因此在uint32_t f不需要填充的情况下,但是当f是 a时uint64_t,添加 4 个字节的填充f以对齐 8 个字节。

出于这个原因,最好将数据成员从最大到最小排序。那么就不需要填充或包装(可能在结构的末尾除外)。

于 2017-02-22T13:14:50.953 回答