问个关于内存对齐的问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
mingl0280
V2EX    C++

问个关于内存对齐的问题

  •  
  •   mingl0280
    mingl0280 2021-10-23 01:44:49 +08:00 3807 次点击
    这是一个创建于 1481 天前的主题,其中的信息可能已经有所发展或是发生改变。

    为啥

    struct FiedLengthHeader { 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)的内存呢……

    23 条回复    2022-01-02 23:53:03 +08:00
    secondwtq
        1
    secondwtq  
       2021-10-23 01:50:01 +08:00   1
    你这个 FixedLengthHeader 在我这是 24 啊 ...

    http://www.catb.org/esr/structure-packing/
    mingl0280
        2
    mingl0280  
    OP
       2021-10-23 01:56:45 +08:00
    @secondwtq 啊抱歉,应该说是 MSVC/g++,64 位。
    我也不知道为啥第一个会只用了 20 字节,第二个用了 24,之前搞得我很蛋疼(我以为第一个也是 24bytes )
    secondwtq
        3
    secondwtq  
       2021-10-23 02:11:28 +08:00
    你是不是#pragma pack(4)了
    mingl0280
        4
    mingl0280  
    OP
       2021-10-23 02:56:26 +08:00
    @secondwtq 没有,两个写法都没有。
    就是因为没加 Pack 才会觉得奇怪的。
    dangyuluo
        5
    dangyuluo  
       2021-10-23 03:41:32 +08:00
    你可以试着看看每个成员的 offset 。不过我测试 MSVC, Gcc, Clang 下的 sizeof 都是 24

    https://godbolt.org/z/c1TM4778j
    secondwtq
        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
    LifStge
        7
    LifStge  
       2021-10-23 06:59:14 +08:00
    测试代码 用最干净的测试 或者自己有对齐要求的 就主动 push 设置 然后在 pop 你无法保证加进来的其他代码是否改变了默认值
    不要因为未知的或可能的设置 来得出自认为有问题的结论
    smdbh
        8
    smdbh  
       2021-10-23 07:09:16 +08:00
    一般来说,例如 uint32_t 需要在整除 4 的地址上,16 就是整除 2 的,HeaderVer 后面有 2 个字节的空隙,PackagerVer 才对齐 。
    Ediacaran
        9
    Ediacaran  
       2021-10-23 08:36:30 +08:00
    低于 32 位的类型后面会空出位置,你可以用调试器或者用 offsetof 宏看一下每个成员的偏移
    非对其访问需要额外的指令操作,所以除非声明了 pack,否则编译器会默认对齐
    elfive
        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()
    elfive
        11
    elfive  
       2021-10-23 08:51:34 +08:00 via iPhone
    @elfive #10 最后那个#pragma pop 写错了,应该是#pragma pack(pop)
    mingl0280
        12
    mingl0280  
    OP
       2021-10-23 09:23:17 +08:00 via Android
    @secondwtq 生成出来的二进制文件我按着字节看的,第一个 int 是 0x14,真就是 20 字节。看到的时候人都给我搞懵逼了……
    mingl0280
        13
    mingl0280  
    OP
       2021-10-23 09:25:06 +08:00 via Android
    @secondwtq 该文件引用的头文件中(只有一个十来行的自定义头文件)并没有其他的 pack 指令(其它头文件都是标准库),我挨个核对过
    @elfive
    NoAnyLove
        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,好像没啥问题
    jones2000
        15
    jones2000  
       2021-10-23 10:24:15 +08:00
    强制 1 字节对齐
    westtide
        16
    westtide  
       2021-10-23 11:07:26 +08:00
    ABI 规范只定义了变址的 对齐方式,并没有定义变蜇的分配顺序 编译器可以自由决定使用何种顺序来分配变量 。
    对于由基本数据类型构造而成的 struct 结构体数据,为了保证其中每个成员都满足对齐要 求,i386 System V ABI 对 strucl 结构体数据的对齐方式有如下几条规则:
    整个结构体变显的 对齐方式与其中对齐方式最严格的成员相同;
    每个成员在满足其对齐方式的前提下,取最小的可用位置作为成员在结构体中的偏移址,这可能导致内部插空;
    结构体大小应为对齐 边界长度的整数倍,这可能导致尾部插空 。

    《计算机系统基础 第 2 版 袁春风余子濠 编著》
    liuxu
        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 位空间
    bigHentai
        18
    bigHentai  
       2021-10-23 16:35:30 +08:00
    我记得默认是按结构体中最大那个变量的字节数对齐,所以是 uint64_t,也就是 8 字节对齐,上面那个默认应该是 24 ,下面的应该是 32 ,你应该是开了强制 4 字节对齐之类的?
    liuxu
        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)
    liuxu
        20
    liuxu  
       2021-10-24 14:06:04 +08:00
    还有一种情况,系统和编译器都是 64 位,编译目标编译出了 32 位和 64 位程序,debug 分析的时候选择了 32 位程序。
    jackchenly
        21
    jackchenly  
       2022-01-02 21:47:47 +08:00
    4+8+(4,DevFlag,HeaderVer )+4+4
    mingl0280
        22
    mingl0280  
    OP
       2022-01-02 22:34:00 +08:00 via Android
    @liuxu 你这个是对的。有人改了我的 VS 配置文件把 64 位编译的 flag 开成了 32 位,然后第一个就挂了。谢谢。
    liuxu
        23
    liuxu  
       2022-01-02 23:53:03 +08:00
    @mingl0280 客气
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1240 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 17:47 PVG 01:47 LAX 09:47 JFK 12:47
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86