## [Bug] macOS 测试二进制:`static_stdlib = false` 失效(#124 文档化语义自 #202 起被静默收紧)+ 静态 libc++ 初始化器顺序导致用户全局对象在静态初始化期访问 `std::cout` 必崩 **版本**: v2026.8.2.2(0.0.86 起受影响;0.0.109 / 当前 main 同样存在) **平台**: macOS(arm64),llvm@20.1.7 + lld,工具链自带 libc++ 20 ### 问题一:`[build] static_stdlib = false` 对测试二进制失效(回归) #124(0.0.52)把 macOS 静态 libc++ 设为默认时明确声明: > "test binaries stay dynamic (per-unit stdlib, cargo-test stance). Opt-out: `[build] static_stdlib = false` (host-coupled dynamic libc++)" 但 #202/#203(0.0.86,A1)把测试二进制改为与分发目标相同的静态 `-load_hidden` libc++ 后,这个 opt-out **对测试二进制静默失效**: - `src/build/flags.cppm:636` 的 `ldStdlibDefault`(分发目标)尊重 `staticStdlib`; - `src/build/flags.cppm:678` 的 `ldStdlibTest`(测试二进制,经 `src/build/ninja_backend.cppm:1220-1225` 按 `LinkUnit::TestBinary` 选择)**无条件**注入 `-nostdlib++ -Wl,-load_hidden,libc++.a -Wl,-load_hidden,libc++abi.a`,不看 `staticStdlib`。 结果 `docs/05-mcpp-toml.md` 至今仍写着 "Set `static_stdlib = false` to fall back to the dynamic system libc++"——文档与行为不一致。我们理解 A1 的动机(测试曾因头文件/系统 dylib 版本分裂炸出 `__hash_memory`;工具链 dylib 又向上链接 `/usr/lib/libc++` 导致 double-free)——但 `staticStdlib=false` 时回到动态 `-lc++` 正是当年 #124 已验证过的形态。 ### 问题二:静态 libc++ 下初始化器顺序错误,用户全局对象访问 `std::cout` 启动即崩 任何"用户全局对象在静态初始化期使用 `std::cout`"的程序在 macOS 上 SIGSEGV(exit 139)。实际触发:mcpp-index 的 boost-ext.ut 模块包(mcpplibs/mcpp-index#142)——测试程序静态初始化期构造 `cfg` 全局对象时执行 `std::cout.rdbuf()` 崩溃(`frame #0: reporter_junit::reporter_junit at ut.hpp:1620`,地址 `0xffffffffffffffe8` = `std::cout` **vptr 为零**,流从未构造)。 根因(经实验证实): 1. 链接行 `$cxx $in -o $out $ldflags $unit_ldflags`(`ninja_backend.cppm:781`),静态库在目标文件之后; 2. Mach-O `__init_offsets` 顺序 = 链接顺序:用户 TU 的初始化器在前,**从静态库提取的 libc++ 成员 `_GLOBAL__I_000100`(`__start_std_streams`,真正构造流的那个,`init_priority(101)`)排最后**(实测 19 个中第 19 位,未执行)。lld 没有按 `init_priority` 排序静态库成员; 3. libc++ 与 libstdc++/MSVC STL 不同:`<iostream>` 头文件无 `ios_base::Init` 守卫,流构造只存在于库内对象 → 顺序完全依赖链接器排序。Windows/Linux 不崩正是因头文件自带守卫。 **对照实验**(同一组目标文件,只改链接参数):E 动态系统 `libc++.1.dylib` ✅ / F 复现 mcpp 静态 `-load_hidden` ❌ 139。这是 #117 修掉的"split-brain"之外的**第二种失败模式**——`-load_hidden` 恰好无法覆盖它。包内修复(模块 TU 前置流构造、`constructor` 属性、显式调用 `DoIOSInit` 等 9 种)全部失败(最接近的一例在 `cfg` 前真正调用了 `DoIOSInit::DoIOSInit()`,`std::cout` 仍为零——静态库符号的 ODR 实体分裂无法在模块侧弥合)。 ### 复现 ```cpp #include <iostream> struct G { G() { std::cout.rdbuf(); } } g; int main() {} ``` `mcpp build && mcpp run` 即崩;加 `[build] static_stdlib = false` 依然崩(问题一)。完整证据链(`__init_offsets` 顺序、nm/反汇编、实验矩阵):https://github.com/ZheFeng7110/mcpp-index/pull/1 ### 建议修复(按推荐序) 1. **`ldStdlibTest` 跟随 `staticStdlib`**:`flags.cppm:678` 的注入块加上与 `ldStdlibDefault` 相同的 `f.staticStdlib` 门控,恢复 #124 文档化的 opt-out 语义(实验 E 已证明动态形态完全正常)。 2. **`static_stdlib` 允许按平台条件化**(#263 的 `kKnownConditionalBuildKeys` 增加该键,或独立通道),使工程只对 macOS 关闭静态 stdlib,不波及 Linux(`-static-libstdc++`)与 MinGW(`-static`)。 3. **修 lld 初始化器排序**:对静态库提取的 `init_priority` 初始化器按优先级排序(治本,成本高)。 修复 1+2 组合最小且兼容既有文档;若接受可提 PR(含 e2e:`static_stdlib=false` 时 macOS 测试二进制 `otool -L` 含 `libc++.1.dylib` 且 repro 通过)。
Activity
docs: record what shipped and what the CI rounds corrected (#336)
fix(build): make the C++ runtime contract one decision (#336) (#337)
chore(ci): drop the temporary macOS crash-diagnostic workflow
chore(ci): pin mcpp 2026.8.3.3 + move the index floor; bypass the glo…
feat: add boost-ext.ut 2.3.1 C++23 module package (#142)
feat(bench): 矩阵规范 SPEC.md + matrix.json —— OS × 工具链 × 引擎 × 工程 × 场景
Mach-O: the measured runtime identity split is reported; the stream-i…
One C++ runtime per process, the graph and pack facts, bounded offlin…