
① 钩子同一个 .cpp两个系统同一个.cpp在两个系统上编译出完全不同的东西Windows 上sizeof(long) 4Linux 上是 8Windows 上wchar_t 2Linux 上是 4同一个struct { char c; long l; }Windows 上是 8 字节、Linux 上是 16 字节同一个函数add(int,int)Windows 的 g 导出符号叫_Z3addiiMSVC 会叫?addYAHHHZ。这些差异统称ABIApplication Binary Interface——函数该怎么调用、类型占多大、符号叫什么的二进制级约定。这一集既是类型大小/对齐/符号的系统梳理也是全系列 23 集的收官把散落在前 22 集的 ABI 线索调用约定、对齐、mangling、异常展开收拢成一幅完整图景。② 源码 vs 实测对照Windows 侧本机 g 15.2.0 实跑E23_abi.exe 本机 ABI (Windows, MinGW g 15.2.0) char1 short2 int4 long4 longlong8 void*8 wchar_t2 struct S{char c; long l;} sizeof8 offsetof(l)4Linux 侧clang 交叉编译E23_linux_probe.cppstatic_assert编译期验证static_assert(sizeof(long)8,LP64 long8 (Linux, 与 Windows 的 4 不同));static_assert(sizeof(wchar_t)4,LP64 wchar_t4 (Linux; Windows 是 2));static_assert(sizeof(S)16,char 7 padding 8-byte long 16);static_assert(__builtin_offsetof(S,l)8,offsetof(l)8);交叉编译通过全部断言满足——这是验证而非听说Linux LP64 下long8、wchar_t4、S{char,long}16/offsetof8。顺带用--targetaarch64-none-elf验证了ARM64 Linux 同样是 LP64long8。结论同样的类型Windows 用 LLP64long4Linux/Unix 用 LP64long8。符号名name mangling——本机 nm 实测$ nm E22_arch.o 0000000000000000 T _Z3addii ; gItanium ABI mangling_Z 3 add iig / clang 用Itanium ABI的 mangling_Z3addii_Z前缀 长度3 名字add 参数类型i(int)i(int)。MSVC 用另一套?addYAHHHZ?开头 分隔 返回类型 参数表。本机无 MSVC此项为文档参考未在本机验证——但 g 的_Z3addii是实测的。结构体对齐——为什么 Windows 是 8、Linux 是 16structS{charc;longl;};// long 是关键的变量Windowslong4对齐 4 →c占 1 字节 3 填充 l占 4 8。Linuxlong8对齐 8 →c 7 填充 l16。同一个struct一个 8 字节一个 16 字节——跨平台传递二进制结构体时最经典的坑。③ 为什么这么设计ABI 是二进制契约它规定调用约定参数放哪些寄存器/栈、类型大小与对齐、符号命名、异常展开LSDA、RTTI 布局、虚表布局。同一平台内所有编译器必须互相兼容否则连库都链接不上。LLP64 vs LP64 是历史选择Windows 为保证 32 位时代long语义的源码兼容选了 LLP64long保持 4只有指针是 64Unix 系选了 LP64long变 8配合指针 8。两种都有道理结果是sizeof(long)成为跨平台第一大坑。为什么有 name manglingC 需要重载add(int,int)和add(double,double)必须导出不同的符号名。Itaniumg/clang与 MSVC 各自定了一套编码规则互不兼容——所以同一个.o不能混用。异常/RTTI 的 ABI 差异Itanium ABI 用基于表驱动的展开LSDA见 E10MSVC 用基于帧的__CxxFrameHandler。跨编译器抛出异常跨库边界是未定义行为。本系列一直用的 MinGW它在 Windows 上用 PE-COFF 目标格式 Win64 调用约定rcx/rdx/r8/r9 shadow space见 E3/E4但 C 层遵循 Itanium ABI 的 mangling——所以_Z3addii既出现在本机 nm 里也和 Linux g 一致。④ 深入一把全系列的 ABI 线索收拢成一张表本集是收官把散落各集的 ABI 相关事实排在一起维度本系列出处WindowsLinux目标文件格式E02PE/COFFELF整数参数寄存器x86-64E03/E22rcx/rdx/r8/r9rdi/rsi/rdx/rcx/r8/r9影子空间E03有32B无sizeof(long)本集48sizeof(wchar_t)本集24C 符号 mangling本集ItaniumMinGW 下Itanium异常展开E10LSDAItanium/MSVC 帧LSDA虚表布局E08/E09vptr 首成员vptr 首成员ABI 不是一个开关而是一整套约定的叠加目标格式、调用约定、类型宽度、对齐、符号、异常——每一层都可能在不同平台/编译器间不同。这也是为什么能编译≠能跨平台。⑤ 深入二为什么extern C能跨语言/跨平台跨语言调用C ↔ C、或 C ↔ 动态库接口的通行做法externCintadd_c(inta,intb);// 用 C 符号不 manglingextern C让函数导出无 mangling 的 C 符号如add_cC 符号规则被几乎所有平台/编译器遵守C ABI 是最小公约数代价失去重载/命名空间——但换来可移植的二进制接口。为什么这能跨平台C ABI 的调用约定参数寄存器/栈规则各平台虽有差异但符号名函数名这一点是通用的且配合固定宽度类型int32_t等可以构造可移植接口。C 是二进制互操作的 lingua franca——这也是 DLL/SO 导出 API 都走 C 接口的原因。⑥ 常见误区误区 1“sizeof是语言标准定的”不——标准只定最小值如long ≥ int具体值由 ABI 定。写跨平台代码必须用cstdint固定宽度类型。误区 2“wchar_t到处是 2 字节”Windows 是 2Linux 是 4。需要明确宽度用char16_t/char32_tC11 起标准固定宽度。误区 3“同一个.o能随便链接”不同编译器g vs MSVC的符号、异常、虚表、RTTI 不兼容。同一平台也别混。误区 4“#pragma pack改了就是真布局”pack 只改对齐不改sizeof(long)的 ABI 值要真可移植字段类型也要固定宽度。误区 5“能在 Windows 上跑 跨平台”类型宽度/对齐/大小端/调用约定都可能在另一平台变。每平台实测本集 static_assert 法才是验证。误区 6“ABI 只是库开发者的事”应用开发者照样踩坑——sizeof(long)、#pragma pack、动态库导出、序列化字节序只要你的程序会跨平台/跨进程/跨语言ABI 就和你有关。误区 7“alignof和sizeof一样”alignof是对齐要求、sizeof是含填充的总体积。结构体的 sizeof 常是 alignof 的整数倍E06 布局规则的延伸两者别混。⑦ 实战启示跨平台别依赖sizeof(long)需要明确宽度用int32_t/int64_tcstdint而不是赌 ABI。跨平台传递结构体前先查对齐与填充网络/文件协议里用#pragma pack或显式固定宽度字段避免两个平台读出不同布局。别混用不同编译器编译的 C 二进制符号、异常、虚表都可能不兼容跨语言边界优先用 C ABIextern C 固定宽度类型。用static_assert把 ABI 假设变成编译期检查static_assert(sizeof(T)expected)能在换平台时立刻爆炸给你看而不是运行期静默出错。序列化/网络协议明确字节序写htobe32/le32toh之类别依赖本机大小端——ABI 之外字节序是跨平台的第二陷阱。⑧ 扩展专题一固定宽度类型cstdint的正确用法#includecstdintint32_ti32;// 永远 32 位uint64_tu64;// 永远 64 位int_least32_t;// 至少 32 位保证存在的最小类型int32_t等如果平台没有正好该宽度的类型编译失败不存在则无定义——这是故意的宁可编译失败也不静默错位int_least32_t保证至少 32 位的最窄类型跨平台总存在跨平台文件/网络格式一律固定宽度类型 明确字节序。为什么重要int/long的宽度是 ABI 决定固定宽度类型把宽度从 ABI 里抽出来变成语言保证——写一次到处宽度一致。⑨ 扩展专题二大小端与二进制兼容小端x86/ARM 常见低字节在低地址——0x12345678内存为78 56 34 12大端网络字节序高字节在低地址——12 34 56 78跨平台陷阱把int32_t直接写入文件/网络另一个大小端不同的机器读出来值会翻转。解法序列化时显式转网络序大端反序列化时转主机序uint32_tbehtonl(host_value);// 主机序 → 网络序大端uint32_thostntohl(be);// 反解工程含义任何跨机器持久化/传输的数据都要明确字节序——这不是高级优化是正确性要求。本集的主题 ABI 管内存内布局字节序管内存与字节流的映射两者一起才构成二进制兼容。⑩ 扩展 FAQQItanium ABI 和 MSVC ABI 能互通吗AC ABIextern C 固定宽度可以C ABI类、异常、虚表不能。DLL 导出 C 类到其他编译器 自找麻烦。Q为什么long long到处是 8 字节Along long标准要求至少 64 位且几乎所有 ABI 都实现为 8 字节——它比long更可预测。Q#pragma pack(1)能省多少A省填充字节但代价是未对齐访问E20 讲过可能 UB/变慢与可移植性。只在协议/文件格式里用别用于热数据结构。Qoffsetof跨平台可靠吗A对标准布局类型可靠E06 讲过对非标准布局是 UB。跨平台用它前先确认类型标准布局。Q本系列为什么选 MinGW 而不是 MSVCAMinGW 用 g与 Linux 同编译器、同 Itanium mangling、同命令行便于Windows 实跑 Linux 交叉编译对比——ABI 差异是平台差异不是编译器差异用同一编译器更能看清。⑪ 扩展实验本机 ABI 全查跑E23_abi.cpp打印所有基础类型宽度 结构体对齐和本集数字对照。LP64 交叉验证clang --targetx86_64-linux-gnu -c E23_linux_probe.cpp确认 static_assert 全部通过编译期验证。ARM64 再验证--targetaarch64-none-elf同样编译确认 ARM64 Linux 也是 LP64long8。符号观察nm E22_arch.o看_Z3addii用extern C重编看符号变add不 mangling。大小端探测写程序打印0x12345678的内存字节memcpy到unsigned char[]确认本机小端用htonl转网络序再看字节翻转。⑬ 扩展专题三跨平台开发的检查清单收官工具篇把本集与全系列的知识整理成跨平台动工前清单类型宽度全部用cstdint固定宽度类型int32_t/uint64_tstatic_assert锁关键尺寸字节序持久化/网络一律显式大小端转换htonl/ntohl等对齐与填充跨平台结构体要么#pragma pack 固定宽度要么用序列化函数逐字段读写调用约定/符号库边界用extern C 固定宽度不导出 C 类编译器一致性同一平台内不混 g/MSVC 编译的 C 二进制每平台实测CI 加 Windows/Linux/ARM 三目标编译 测试本集交叉编译法就是没真机也能验。⑭ 扩展 FAQ第二轮Q为什么本机 Windows 的 g 符号是_Z3addii而不是 MSVC 的?add...A因为 MinGW 的 g 用 Itanium ABI 的 mangling与 Linux g/clang 一致MSVC 才用另一套。同一平台的不同编译器C ABI 也可能不同——本集全系列就用 MinGW 统一这一点。Qchar16_t和wchar_t关系Achar16_t标准固定 16 位UTF-16 单元、char32_t固定 32 位wchar_t宽度由平台定。需要明确宽度用 char16_t/char32_t别用 wchar_t。QABI 会随编译器版本变化吗A会——ABI 是约定但会演进C11 曾因 ABI 变化导致库不兼容。大版本间要重新编译所有二进制这也是源码兼容 ≠ ABI 兼容。Q-fabi-version是什么AGCC 的 ABI 版本开关允许显式指定 mangling/布局规则版本。一般不用动但排查库不兼容时可查。QRTTI 跨平台如何ARTTI 名称编码也是 ABI 的一部分Itanium 编码类型名跨编译器typeid字符串格式不同。别解析 typeid 字符串做逻辑。⑮ 扩展实验第二轮extern C对比同一函数加/不加extern Cnm看符号从_Z3addii变add。大小端工具用std::bit_cast/memcpy打印整型内存字节再htonl对比翻转。pack 对比#pragma pack(1)前后sizeof/offsetof变化反汇编看未对齐访问的指令差异。固定宽度重构把代码里long全换成int64_tstatic_assert保证跨平台宽度一致。跨编译器 ABI 说明若装了 MSVC 可对比?addYAHHHZ没有则用文档 nm 对照理解 Itanium 编码规则。⑯ 扩展专题四Itanium ABI 的 mangling 规则——读懂_Z3addii_Z3addii不是随机字符是 Itanium ABI 的可逆编码_Z 3 add i i │ │ │ │ └ 参数类型编码iint │ │ │ └── 第二个参数 │ │ └────── 函数名add长度 3 │ └───────── 名字长度 └──────────── mangled name 前缀i intl longd doubleN...E 命名空间/类作用域v void重载靠参数类型编码区分add(int,int)→_Z3addiiadd(double,double)→_Z3adddd反解cfilt _Z3addii→add(int, int)本机 MinGW 带 cfilt。为什么你要懂链接报undefined reference to _Z...时能反解出哪个函数、什么参数调试器栈回溯里的 mangled 符号能看懂跨编译器/平台发现符号不匹配时能判断是参数不匹配还是ABI 体系不同。这是符号名这一层 ABI 的解剖——和本集类型宽度那层一起构成完整 ABI 图景。⑰ 扩展专题五动态库DLL/SO导出与 ABI 陷阱跨平台开发绕不开动态库ABI 陷阱集中爆发导出符号Windows DLL 用__declspec(dllexport)Linux SO 默认导出全部符号——导出面不同类导出导出 C 类 导出它的 vtable、RTTI、异常展开——换编译器/版本就崩惯例库边界只导出extern C函数 固定宽度 简单数据结构或序列化。#ifdef_WIN32#defineEXPORT__declspec(dllexport)#else#defineEXPORT__attribute__((visibility(default)))#endifexternCEXPORTint32_tlib_add(int32_ta,int32_tb);工程含义动态库边界 最严格的 ABI 边界。守住extern C 固定宽度 明确字节序才能跨编译器/跨版本稳定加载。这也是为什么 C 库要包一层 C API的机器级原因。⑱ 扩展 FAQ第三轮Qcfilt是干什么的A把 mangled 符号还原成人名_Z3addii→add(int, int)。调试、链接报错排查的标配工具本机 MinGW 自带。Q为什么long在 Windows 是 4 却在 Linux 是 8ALLP64 vs LP64 的历史选择本集③。这也是可移植代码用固定宽度类型的根因。Q#pragma pack能不能解决跨平台结构体A能解决对齐这一层但字段宽度long4 vs 8它管不了。pack 固定宽度类型一起用才完整。Q为什么要每平台编译一遍AABI 差异在编译期就能暴露static_assert、运行期才暴露布局错位。编译期炸比运行期炸好一万倍。Q本系列实测汇编的招牌对跨平台意味着什么A意味着每个数字都能复现——你在 Linux 编同一份看到同样的_Z3addii、同样的对齐。亲眼看见比听说可靠这正是 E23 收官要强调的。Qstd::filesystem::path的字符串跨平台安全吗A路径内部用原生编码Windows UTF-16 /wchar_t2Linux UTF-8 /char跨平台要显式转换.u8string()/.wstring()——又一个ABI 渗透到 API的例子。Qint8_t/char的符号性跨平台一致吗Achar的符号性由平台定本机 x86 是有符号某些 ARM 默认无符号int8_t固定 8 位有符号。涉及char做数值时要小心符号性差异。⑲ 扩展实验第三轮cfilt 实战cfilt _Z3addii _Z3adddd _Z3addll验证 mangling 反解。重载符号观察写add(int,int)和add(double,double)nm对比_Z3addii/_Z3adddd。命名空间 mangling函数放进命名空间/类nm看N...E编码。DLL/SO 导出宏写lib_add的跨平台导出宏Windows/Linux 各编一次确认符号导出。pack固定宽度构造#pragma pack(1)int32_t结构体两平台含 ARM 交叉验证 sizeof 一致。⑳ 扩展专题六从 E01 到 E23——读汇编能力的四层进化收官之际回看整个系列培养的读汇编能力分四层看指令E01~E05认得出mov/add/call/ret理解源码一行 几条指令看结构E06~E15从指令里看出对象布局、虚表、闭包、容器——“代码的长相”看优化E16~E21读懂编译器在做什么手术内联/向量化/删除、以及它凭什么是合法的UB 授权看平台E22~E23知道同样的源码在不同 CPU/系统上如何不同——“代码的国籍”。最终能力 四层叠加拿到一份陌生 C你能a预测它会编译成什么样b在.s里验证c解释为什么优化成这样d判断换平台会怎么变。这就是从相信文档到亲眼看见的完整闭环。