为啥
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)的内存呢……
1
secondwtq 2021-10-23 01:50:01 +08:00 1
|
2
mingl0280 OP @secondwtq 啊抱歉,应该说是 MSVC/g++,64 位。
我也不知道为啥第一个会只用了 20 字节,第二个用了 24,之前搞得我很蛋疼(我以为第一个也是 24bytes ) |
3
secondwtq 2021-10-23 02:11:28 +08:00
你是不是#pragma pack(4)了
|
5
dangyuluo 2021-10-23 03:41:32 +08:00
|
6
secondwtq 2021-10-23 03:50:11 +08:00
我猜可能哪个 header 里面 #pragma pack push 了忘 pop 了 ... 你可以 -E 一下看看。实在不行一点点 reduce,能 reduce 到放在 godbolt 上面的程度更好
你这个大小是怎么观察到的?是直接 sizeof 还是? 另外 Clang 有试过么? Clang 有个好处是想要折腾比较方便,比如我加个 #pragma pack 就可以在 AST dump 里看到: https://gist.github.com/secondwtq/e5ce6e72fe80900d54fa0eb44d4487d4 |
7
LifStge 2021-10-23 06:59:14 +08:00
测试代码 用最干净的测试 或者自己有对齐要求的 就主动 push 设置 然后在 pop 你无法保证加进来的其他代码是否改变了默认值
不要因为未知的或可能的设置 来得出自认为有问题的结论 |
8
smdbh 2021-10-23 07:09:16 +08:00
一般来说,例如 uint32_t 需要在整除 4 的地址上,16 就是整除 2 的,HeaderVer 后面有 2 个字节的空隙,PackagerVer 才对齐 。
|
9
Ediacaran 2021-10-23 08:36:30 +08:00
低于 32 位的类型后面会空出位置,你可以用调试器或者用 offsetof 宏看一下每个成员的偏移
非对其访问需要额外的指令操作,所以除非声明了 pack,否则编译器会默认对齐 |
10
elfive 2021-10-23 08:50:23 +08:00 via iPhone
这种对对齐字节有要求的场景请务必手动指定对齐字节位……
因为在编译时,这段代码很有可能会受到 1. 编译器默认对齐字节数 2. 代码中其他地方定义的对齐数 的影响而产生不可预计的影响。 字节对齐的语句也要成对出现,避免对其他地方的影响: #pragma pack(n) // 设置对齐字节数 #pragma pack() // 取消设置,恢复默对齐字节数 或者: #pragma pack(push) #pragma pack(n) #pragma pop() |
11
elfive 2021-10-23 08:51:34 +08:00 via iPhone
@elfive #10 最后那个#pragma pop 写错了,应该是#pragma pack(pop)
|
12
mingl0280 OP @secondwtq 生成出来的二进制文件我按着字节看的,第一个 int 是 0x14,真就是 20 字节。看到的时候人都给我搞懵逼了……
|
13
mingl0280 OP |
14
NoAnyLove 2021-10-23 09:51:16 +08:00 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,好像没啥问题 |
15
jones2000 2021-10-23 10:24:15 +08:00
强制 1 字节对齐
|
16
westtide 2021-10-23 11:07:26 +08:00
ABI 规范只定义了变址的 对齐方式,并没有定义变蜇的分配顺序 编译器可以自由决定使用何种顺序来分配变量 。
对于由基本数据类型构造而成的 struct 结构体数据,为了保证其中每个成员都满足对齐要 求,i386 System V ABI 对 strucl 结构体数据的对齐方式有如下几条规则: 整个结构体变显的 对齐方式与其中对齐方式最严格的成员相同; 每个成员在满足其对齐方式的前提下,取最小的可用位置作为成员在结构体中的偏移址,这可能导致内部插空; 结构体大小应为对齐 边界长度的整数倍,这可能导致尾部插空 。 《计算机系统基础 第 2 版 袁春风余子濠 © 编著》 |
17
liuxu 2021-10-23 11:17:31 +08:00
你下面的不是
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 位空间 |
18
bigHentai 2021-10-23 16:35:30 +08:00
我记得默认是按结构体中最大那个变量的字节数对齐,所以是 uint64_t,也就是 8 字节对齐,上面那个默认应该是 24 ,下面的应该是 32 ,你应该是开了强制 4 字节对齐之类的?
|
19
liuxu 2021-10-24 14:01:37 +08:00 1
@liuxu #17 修正一下,我之前是 32 位系统 debug 的,现在都是 64 位系统,情况有变化。
首先说结论: 1. 和编译的目标程序的位数有关,32 位程序最高是 4 字节(32 位)对齐,64 位程序最高是(8 字节)64 位对齐。 2. 在 1 的要求下,struct 中按最宽位数的变量对齐。结合 1 中的“最高”的意思是: 64 位程序:uint32_t + uint64_t + uint64_t 分配 24 字节(8+8+8)。 32 位程序:uint32_t + uint64_t + uint64_t 分配 20 字节(4+(4+4)+(4+4))。 32/64 位程序:uint8_t +uint16_t +uint8_t 分配 8 字节(2+2+2)。 3. 添加#pragma pack(4)后,32 位和 64 位程序都按 4 字节(32 位)对齐,也就是 uint32_t + uint64_t + uint64_t 都是 20 字节(4+(4+4)+(4+4))。但是如果 struct 最大为 uint16_t ,则依然按 2 字节对齐。(也就是说 pack 无法影响 struct 最大位宽限制) 4. 添加了#pragma pack(16)后,64 位依然按 8 字节(64 位)对齐。也就是说 pack 中的数字只能是不大于(系统位数 /8)的 2 的次方的数字。 所以楼主的情况, 如果代码中真的没有#pragma pack(),但是出现了帖子中的现象,只有可能是以下 3 中情况: 1. 楼主的系统是 32 位的。( 32 位系统只会运行 32 位编译器编译出 32 位程序) 2. 楼主的编译器是 32 位的。( 32 位编译器只会编译出 32 位程序) 3. 楼主 64 位 msvc 编译目标选择的是 32 位程序。(据我所知 vs 默认的 debuging 版本编译的是 32 位程序,至少我几年前 debug 的时候是的,也有可能是我创建项目的时候设置成了 32 位)。 即楼主 2 次编译的字节对齐方式为: struct FixedLengthHeader { uint32_t HeaderSize = 0; uint64_t CryptogramSize = 0; uint64_t ReservedField = 0; }FixedPackageHeaders; 20 字节(4+(4+4)+(4+4))。 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+(4+4)+4+4+4)。 以上是 64 位 linux gcc 下的结果推测的 msvc 的结果,windows 下的实际结果还是得大佬们自己调试。 以下是我 GDB 打印情况: 系统:Ubuntu 20.04.3 LTS x86_64 gcc: gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 没有添加#progma pack(4),编译指令:gcc -g test.c /* offset | size */ type = struct FixedLengthHeader { /* 0 | 4 */ uint32_t HeaderSize; /* XXX 4-byte hole */ /* 8 | 8 */ uint64_t CryptogramSize; /* 16 | 8 */ uint64_t ReservedField; /* total size (bytes): 24 */ } (8+8+8) /* offset | size */ type = struct FixedLengthHeader { /* 0 | 4 */ uint32_t HeaderSize; /* XXX 4-byte hole */ /* 8 | 8 */ uint64_t CryptogramSize; /* 16 | 1 */ uint8_t DevFlag; /* 17 | 1 */ uint8_t HeaderVer; /* XXX 2-byte hole */ /* 20 | 4 */ uint32_t PackagerVer; /* 24 | 2 */ uint16_t Reserved; /* XXX 6-byte padding */ /* total size (bytes): 32 */ } (8+8+8) /* offset | size */ type = struct FixedLengthHeader { /* 0 | 4 */ uint32_t HeaderSize; /* 4 | 1 */ uint8_t DevFlag; /* 5 | 1 */ uint8_t HeaderVer; /* XXX 2-byte hole */ /* 8 | 4 */ uint32_t PackagerVer; /* 12 | 2 */ uint16_t Reserved; /* XXX 2-byte padding */ /* total size (bytes): 16 */ } (4+4+4+4) 没有添加#progma pack(4),编译指令:gcc -m32 -g test.c /* offset | size */ type = struct FixedLengthHeader { /* 0 | 4 */ uint32_t HeaderSize; /* 4 | 8 */ uint64_t CryptogramSize; /* 12 | 8 */ uint64_t ReservedField; /* total size (bytes): 20 */ } (4+(4+4)+(4+4)) /* offset | size */ type = struct FixedLengthHeader { /* 0 | 4 */ uint32_t HeaderSize; /* 4 | 8 */ uint64_t CryptogramSize; /* 12 | 1 */ uint8_t DevFlag; /* 13 | 1 */ uint8_t HeaderVer; /* XXX 2-byte hole */ /* 16 | 4 */ uint32_t PackagerVer; /* 20 | 2 */ uint16_t Reserved; /* XXX 2-byte padding */ /* total size (bytes): 24 */ } (4+(4+4)+4+4+4) 添加#progma pack(4),编译指令:gcc -g test.c /* offset | size */ type = struct FixedLengthHeader { /* 0 | 4 */ uint32_t HeaderSize; /* 4 | 8 */ uint64_t CryptogramSize; /* 12 | 8 */ uint64_t ReservedField; /* total size (bytes): 20 */ } (4+(4+4)+(4+4)) /* offset | size */ type = struct FixedLengthHeader { /* 0 | 4 */ uint32_t HeaderSize; /* 4 | 8 */ uint64_t CryptogramSize; /* 12 | 1 */ uint8_t DevFlag; /* 13 | 1 */ uint8_t HeaderVer; /* XXX 2-byte hole */ /* 16 | 4 */ uint32_t PackagerVer; /* 20 | 2 */ uint16_t Reserved; /* XXX 2-byte padding */ /* total size (bytes): 24 */ } (4+(4+4)+4+4+4) 添加#progma pack(16),编译指令:gcc -g test.c /* offset | size */ type = struct FixedLengthHeader { /* 0 | 4 */ uint32_t HeaderSize; /* XXX 4-byte hole */ /* 8 | 8 */ uint64_t CryptogramSize; /* 16 | 1 */ uint8_t DevFlag; /* 17 | 1 */ uint8_t HeaderVer; /* XXX 2-byte hole */ /* 20 | 4 */ uint32_t PackagerVer; /* 24 | 2 */ uint16_t Reserved; /* XXX 6-byte padding */ /* total size (bytes): 32 */ } (8+8+8+8) |
20
liuxu 2021-10-24 14:06:04 +08:00
还有一种情况,系统和编译器都是 64 位,编译目标编译出了 32 位和 64 位程序,debug 分析的时候选择了 32 位程序。
|
21
jackchenly 2022-01-02 21:47:47 +08:00
4+8+(4,DevFlag,HeaderVer )+4+4
|
22
mingl0280 OP @liuxu 你这个是对的。有人改了我的 VS 配置文件把 64 位编译的 flag 开成了 32 位,然后第一个就挂了。谢谢。
|