繁体   English   中英

为什么在类中使用时 uint64_t 比 2 个 uint32_t 需要更多内存? 以及如何防止这种情况?

[英]Why does an uint64_t needs more memory than 2 uint32_t's when used in a class? And how to prevent this?

我以下面的代码为例。

#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 之外。

正如有人指出的那样,这是由于padding

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

#pragma pack(1)

class ... {

};
#pragma pack(pop)

它告诉您的编译器不要对齐 8 个字节,而是对齐 1 个字节。 pop 命令将其关闭(这非常重要,因为如果您在标题中执行此操作并且有人包含您的标题,则可能会发生非常奇怪的错误)

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

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

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

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

为什么在类中使用时 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))来强制编译器比正常对齐要求所允许的更紧密地打包结构。 然而,除了限制可移植性之外,这在获取成员地址或绑定对成员的引用时会产生问题。 结果指针或引用可能不满足对齐要求,因此使用起来可能不安全。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM