1

问个关于内存对齐的问题

 2 years ago
source link: https://www.v2ex.com/t/809945
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

V2EX  ›  C++

问个关于内存对齐的问题

  mingl0280 · 1 天前 · 1310 次点击
struct FixedLengthHeader {
        uint32_t HeaderSize = 0;
        uint64_t CryptogramSize = 0;
        uint64_t ReservedField = 0;
}FixedPackageHeaders;

占用 20 字节( 4+8+8 ),但是如果用下面这个写法,

    struct FixedLengthHeader {
        uint32_t HeaderSize = 0;
        uint64_t CryptogramSize = 0;
        uint8_t DevFlag = 0;
        uint8_t HeaderVer = 0;
        uint32_t PackagerVer = 0;
        uint16_t Reserved = 0
    }FixedPackageHeaders;

会因为内存对齐占用 24 字节(4+8+2+2+4+4)的内存呢……

18 条回复    2021-10-23 16:35:30 +08:00

mingl0280

mingl0280   1 天前

@secondwtq 啊抱歉,应该说是 MSVC/g++,64 位。
我也不知道为啥第一个会只用了 20 字节,第二个用了 24,之前搞得我很蛋疼(我以为第一个也是 24bytes )

secondwtq

secondwtq   1 天前

你是不是#pragma pack(4)了

mingl0280

mingl0280   1 天前

@secondwtq 没有,两个写法都没有。
就是因为没加 Pack 才会觉得奇怪的。

dangyuluo

dangyuluo   1 天前

你可以试着看看每个成员的 offset 。不过我测试 MSVC, Gcc, Clang 下的 sizeof 都是 24

https://godbolt.org/z/c1TM4778j

secondwtq

secondwtq   1 天前

我猜可能哪个 header 里面 #pragma pack push 了忘 pop 了 ... 你可以 -E 一下看看。实在不行一点点 reduce,能 reduce 到放在 godbolt 上面的程度更好

你这个大小是怎么观察到的?是直接 sizeof 还是?

另外 Clang 有试过么? Clang 有个好处是想要折腾比较方便,比如我加个 #pragma pack 就可以在 AST dump 里看到:

LifStge

LifStge   22 小时 37 分钟前

测试代码 用最干净的测试 或者自己有对齐要求的 就主动 push 设置 然后在 pop 你无法保证加进来的其他代码是否改变了默认值
不要因为未知的或可能的设置 来得出自认为有问题的结论

smdbh

smdbh   22 小时 27 分钟前

一般来说,例如 uint32_t 需要在整除 4 的地址上,16 就是整除 2 的,HeaderVer 后面有 2 个字节的空隙,PackagerVer 才对齐 。

Ediacaran

Ediacaran   21 小时 0 分钟前

低于 32 位的类型后面会空出位置,你可以用调试器或者用 offsetof 宏看一下每个成员的偏移
非对其访问需要额外的指令操作,所以除非声明了 pack,否则编译器会默认对齐

elfive

elfive   20 小时 46 分钟前 via iPhone

这种对对齐字节有要求的场景请务必手动指定对齐字节位……
因为在编译时,这段代码很有可能会受到
1. 编译器默认对齐字节数
2. 代码中其他地方定义的对齐数
的影响而产生不可预计的影响。

字节对齐的语句也要成对出现,避免对其他地方的影响:
#pragma pack(n) // 设置对齐字节数
#pragma pack() // 取消设置,恢复默对齐字节数

或者:
#pragma pack(push)
#pragma pack(n)
#pragma pop()

elfive

elfive   20 小时 45 分钟前 via iPhone

@elfive #10 最后那个#pragma pop 写错了,应该是#pragma pack(pop)

mingl0280

mingl0280   20 小时 13 分钟前 via Android

@secondwtq 生成出来的二进制文件我按着字节看的,第一个 int 是 0x14,真就是 20 字节。看到的时候人都给我搞懵逼了……

mingl0280

mingl0280   20 小时 11 分钟前 via Android

@secondwtq 该文件引用的头文件中(只有一个十来行的自定义头文件)并没有其他的 pack 指令(其它头文件都是标准库),我挨个核对过
@elfive

NoAnyLove

NoAnyLove   19 小时 45 分钟前   ❤️ 1

没有用紧凑声明的话就会默认 32 位对齐,出于性能考虑,至少 32 位下是这么的。话说在 64 位中也是 32 位对齐吗?

uint32_t HeaderSize: 4
uint64_t CryptogramSize: 8
uint8_t DevFlag: 1
uint8_t HeaderVer: 1
uint8_t __padding: 1
uint8_t __padding: 1
uint32_t PackagerVer: 4
uint16_t Reserved: 2
uint8_t __padding: 1
uint8_t __padding: 1

4+8+1+1+1+1+4+2+1+1 = 24,好像没啥问题

jones2000

jones2000   19 小时 12 分钟前

强制 1 字节对齐

westtide

westtide   18 小时 29 分钟前

ABI 规范只定义了变址的 对齐方式,并没有定义变蜇的分配顺序 编译器可以自由决定使用何种顺序来分配变量 。
对于由基本数据类型构造而成的 struct 结构体数据,为了保证其中每个成员都满足对齐要 求,i386 System V ABI 对 strucl 结构体数据的对齐方式有如下几条规则:
整个结构体变显的 对齐方式与其中对齐方式最严格的成员相同;
每个成员在满足其对齐方式的前提下,取最小的可用位置作为成员在结构体中的偏移址,这可能导致内部插空;
结构体大小应为对齐 边界长度的整数倍,这可能导致尾部插空 。

《计算机系统基础 第 2 版 袁春风余子濠 © 编著》

liuxu

liuxu   18 小时 19 分钟前

你下面的不是
24 字节(4+8+2+2+4+4 )
而是
24 字节(4+8+4+4+4 )

你试试
struct FixedLengthHeader {
uint32_t HeaderSize = 0;
uint64_t CryptogramSize = 0;
uint8_t DevFlag = 0;
uint8_t HeaderVer = 0;
uint16_t Reserved = 0;
uint32_t PackagerVer = 0;
}FixedPackageHeaders;

应该会变成 4+8+4+4=20

应该是编译器优化结果

8|8|32 的位地址占用情况应该是
1111 1111 1111 1111 xxxx xxxx xxxx xxxx
1111 1111 1111 1111 1111 1111 1111 1111
也就是 2 个 8 后面空着不要了

但是如果 8|8|8/16|32,不会再多分配内存,继续复用没有用的 16 位空间

bigHentai

bigHentai   13 小时 1 分钟前

我记得默认是按结构体中最大那个变量的字节数对齐,所以是 uint64_t,也就是 8 字节对齐,上面那个默认应该是 24 ,下面的应该是 32 ,你应该是开了强制 4 字节对齐之类的?

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK