简体   繁体   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?

I have made the following code as an example.我以下面的代码为例。

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

prints印刷

20
24
8
4

So it's fairly simple to see that one uint64_t is as large as two uint32_t's, Why would class 2 have 4 extra bytes, if they are the same except for the substitution of two uint32_t's for an uint64_t.所以很容易看出一个 uint64_t 和两个 uint32_t 一样大,为什么类 2 会有 4 个额外的字节,如果它们是相同的,除了用两个 uint32_t 替换一个 uint64_t 之外。

As it was pointed out, this is due to padding .正如有人指出的那样,这是由于padding

To prevent this, you may use为了防止这种情况,您可以使用

#pragma pack(1)

class ... {

};
#pragma pack(pop)

It tells your compiler to align not to 8 bytes, but to one byte.它告诉您的编译器不要对齐 8 个字节,而是对齐 1 个字节。 The pop command switches it off (this is very important, since if you do that in the header and somebody includes your header, very weird errors may occur) pop 命令将其关闭(这非常重要,因为如果您在标题中执行此操作并且有人包含您的标题,则可能会发生非常奇怪的错误)

The rule for alignment (on x86 and x86_64) is generally to align a variable on it's size .对齐规则(在 x86 和 x86_64 上)通常是对齐变量的 size

In other words, 32-bit variables are aligned on 4 bytes, 64-bit variables on 8 bytes, etc.换句话说,32 位变量在 4 个字节上对齐,64 位变量在 8 个字节上对齐,等等。

The offset of f is 12, so in case of uint32_t f no padding is needed, but when f is an uint64_t , 4 bytes of padding are added to get f to align on 8 bytes. f的偏移量为 12,因此在uint32_t f情况下不需要填充,但是当fuint64_t ,添加 4 个字节的填充以使f对齐 8 个字节。

For this reason it is better to order data members from largest to smallest .因此,最好将数据成员从大到小排序 Then there wouldn't be any need for padding or packing (except possibly at the end of the structure).那么就不需要填充或包装(除非可能在结构的末尾)。

Why does an uint64_t needs more memory than 2 uint32_t's when used in a class?为什么在类中使用时 uint64_t 比 2 个 uint32_t 需要更多内存?

The reason is padding due to alignment requirements.原因是由于对齐要求而填充。

On most 64-bit architectures uint8_t has an alignment requirement of 1, uint16_t has an alignment requirement of 2, uint32_t has an alignment requirement of 4 and uint64_t has an alignment requirement of 8. The compiler must ensure that all members in a structure are correctly aligned and that the size of a structure is a multiple of it's overall alignment requirement.在大多数 64 位架构上,uint8_t 的对齐要求为 1,uint16_t 的对齐要求为 2,uint32_t 的对齐要求为 4,uint64_t 的对齐要求为 8。编译器必须确保结构中的所有成员都正确对齐并且结构的大小是其整体对齐要求的倍数。 Furthermore the compiler is not allowed to re-order members.此外,编译器不允许重新排序成员。

So your structs end up laid out as follows所以你的结构最终布局如下

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

And how to prevent this?以及如何防止这种情况?

Unfortunately there is no good general solution.不幸的是,没有好的通用解决方案。

Sometimes it is possible to reduce the amount of padding by re-ordering fields, but that doesn't help in your case.有时可以通过重新排序字段来减少填充量,但这对您的情况没有帮助。 It just moves the padding around in the structure.它只是在结构中移动填充。 A structure with a field requiring 8 byte alignment will always have a size that is a multiple of 8. Therefore no matter how much you rearrange the fields your structure will always have a size of at least 24.具有需要 8 字节对齐的字段的结构的大小始终是 8 的倍数。 因此,无论您重新排列字段多少,您的结构的大小始终至少为 24。

You can use compiler-specific features such as #pragma pack or __attribute((packed)) to force the compiler to pack the structure more tightly than normal alignment requirements would allow.您可以使用特定于编译器的功能,例如#pragma pack__attribute((packed))来强制编译器比正常对齐要求所允许的更紧密地打包结构。 However, as well as limiting portability, this creates a problem when taking the address of a member or binding a reference to the member.然而,除了限制可移植性之外,这在获取成员地址或绑定对成员的引用时会产生问题。 The resulting pointer or reference may not satisfy the alignment requirements and therefore may not be safe to use.结果指针或引用可能不满足对齐要求,因此使用起来可能不安全。

Different compilers vary in how they handle this problem.不同的编译器处理这个问题的方式各不相同。 From some playing around on godbolt.来自一些在 Godbolt 上玩耍的人。

  • g++ 9 through 11 will refuse to bind a reference to a packed member and give a warning when taking the address. g++ 9 到 11 将拒绝将引用绑定到打包成员并在获取地址时发出警告。
  • clang 4 through 11 will give a warning when taking the address, but will silently bind a reference and pass that reference across a compilation unit boundary. clang 4 到 11 将在获取地址时发出警告,但会静默绑定一个引用并将该引用传递到编译单元边界。
  • Clang 3.9 and earlier will take the address and bind a reference silently. Clang 3.9 及更早版本将获取地址并静默绑定引用。
  • g++ 8 and earlier and clang 3.9 and earlier (down to the oldest version on godbolt) will also refuse to bind a reference, but will take the address with no warning. g++ 8 及更早版本和 clang 3.9 及更早版本(直到 Godbolt 上最旧的版本)也将拒绝绑定引用,但会在没有警告的情况下获取地址。
  • icc will bind a pointer or take the address without producing any warnings in either case (though to be fair intel processors support unaligned access in hardware). icc 将绑定一个指针或获取地址,在任何一种情况下都不会产生任何警告(尽管公平的英特尔处理器支持硬件中的未对齐访问)。

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

相关问题 为什么在 64 位机器上使用 uint8_t 编写的这段代码比使用 uint32_t 或 uint64_t 编写的类似代码运行得更快? - Why does this piece of code written using uint8_t run faster than analogous code written with uint32_t or uint64_t on a 64bit machine? 为什么在 Mac OS X 上使用 size_t 时 uint32_t 和 uint64_t 之间存在歧义? - Why is there ambiguity between uint32_t and uint64_t when using size_t on Mac OS X? 为什么uint32_t与uint64_t的速度不同? - why a uint32_t vs uint64_t speed difference? 如何以类型安全的方式分配uint32_t :: max和uint64_t的最小值? - How to assign min of uint32_t::max and uint64_t in type safe manner? 如何将 uint32_t 或 uint64_t 存储在 void 指针中 - How can I store a uint32_t or uint64_t in a void pointer 如何让gcc警告从uint32_t到uint64_t的转换? - How to have gcc warn about conversions from uint32_t to uint64_t? 在 C 或 C++ 中,uint64_t 的 memory 布局是否保证与 uint32_t[2] 相同? 可以将一个转换为另一个吗? - In C or C++, is memory layout of uint64_t guaranteed to be the same as uint32_t[2] ? Can one be cast as the other? 将uint32_t转换为uint64_t导致不同的值? - casting uint32_t to uint64_t results in different value? 使用多个uint32_t整数生成uint64_t哈希键 - Generate uint64_t hash key with several uint32_t integers 在 uint32_t 范围内变换 uint64_t 范围 - Transform uint64_t range in uint32_t range
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM