Skip to content

obj/std.o 被无条件链进每个单元:纯 C 的 compat 包因此依赖 libstdc++.so.6 #416

Description

@speak-agent

现象

obj/std.o(import std 的模块对象)被无条件链进每一个 Binary / TestBinary /
SharedLibrary 单元,完全不看该单元是否真的 import std。

src/build/ninja_backend.cppm:1362-1381:

switch (lu.kind) {
    case LinkUnit::Binary:
    case LinkUnit::TestBinary:
        if (has_std_artifacts)
            ins += " " + escape_ninja_path(std_o_dst);   // ← 无条件
        …
    case LinkUnit::SharedLibrary:
        if (has_std_artifacts)
            ins += " " + escape_ninja_path(std_o_dst);   // ← 无条件
        …
}

has_std_artifacts 只表示"这个工具链有预构建的 std 模块",与单元自身的需求无关。

实测:bin/libXau.so 的链接输入 = 8 个纯 C 的 .o + 一个 obj/std.o。
compat.xau 里没有一行 C++。

后果

在 #414 之前,这与共享库的 -static-libstdc++ 叠加,把整个 libstdc++.a 拖进纯 C 的
compat 包:libXau.so 39KB → 9.5MB,并导出 777 个 GLOBAL 标准库定义。

#414 把 ELF 共享库改判 toolchain-coupled 之后体积已经回落(实测 libXau 39 368 B),
但 std.o 仍在,于是纯 C 的 compat 包(libXau / libXdmcp / libX11)凭空多一条
NEEDED libstdc++.so.6:

$ readelf -d bin/libX11.so | grep NEEDED
 … libstdc++.so.6 …        ← compat.x11 是纯 C

一个纯 C 的库不该依赖 C++ 运行时。它现在依赖,只是因为链接输入里多了一个对象。

建议

把 std_o_dst 的追加条件从「工具链有 std 模块」收窄到「这个链接单元真的需要它」。

需要先确认的两点:

  1. import std 的传递性 —— 依赖的 BMI 传递 import std 是有历史坑的
    (见 dep BMI 缓存跨版本毒化那次)。判据不能只看本单元的源码。
  2. 静态库单元:StaticLibrary 走 ar,本来就不追加,不受影响。

判据

  • 一个纯 C 的 compat 包产出的 .so,readelf -d 里没有 libstdc++.so.6
  • 一个 import std 的工程仍正常链接、运行
  • 一个间接通过依赖的模块接口用到 std 的工程仍正常链接(传递性不能漏)

背景

Activity

  1. added a commit that references this issue on Aug 15, 2026
  2. Sunrisepeak commented on Aug 15, 2026

    @Sunrisepeak
    Member

    实施前按方案先复现,结论是这条 issue 的因果不成立 —— 症状是真的,归因是错的。

    实测(mcpp 2026.8.15.1,本分支)

    一个纯 C 的共享库工程(两个 .c,没有一行 C++,没有 import std):

    $ readelf -d bin/libpurec.so | grep NEEDED
      libstdc++.so.6      ← 症状确实在
      libm.so.6
      libgcc_s.so.1
      libc.so.6
    
    $ grep -c 'obj/std.o' target/*/*/build.ninja
      0                   ← build.ninja 里根本没有 std.o
    
    $ nm -D bin/libpurec.so | grep -c _ZGIW
      0                   ← 产物里没有模块初始化器
    

    std.o 没有被链进去,而 libstdc++.so.6 照样在。

    真因

    std.o 本身也不可能是来源:

    $ nm --defined-only  obj/std.o   → 1 个符号:_ZGIW3std(模块 std 的全局初始化器)
    $ nm --undefined-only obj/std.o  → 0 个
    $ ls -l obj/std.o                → 1280 字节
    

    零个未定义符号,它拖不动任何共享库。

    真正的来源是链接驱动。生成的规则是:

    cxx       = .../xim-x-gcc/16.1.0/bin/g++
    rule cxx_shared
      command = $cxx -shared @$out.rsp -o $out $ldflags $soname_flag $unit_ldflags
    

    g++ 隐式追加 -lstdc++,而全仓 --as-needed 只用在 -latomic 那一处
    (src/build/flags.cppm:240),所以 -lstdc++ 会无条件成为 NEEDED —— 即使
    没有任何符号引用它。

    这条 issue 该怎么拆

    原判据「纯 C 的 compat 包产出的 .so,readelf -d 里没有 libstdc++.so.6」
    不可能通过收窄 std.o 达成,两者是独立的两件事:

    1. std.o 无条件链进每个单元 —— 仍然该修(一个不该在那里的对象),但后果比
      本 issue 描述的小得多。风险也比预想低:std.o 唯一的符号是模块初始化器,
      而 import std 的 TU 会引用它,所以谓词若漏判会得到链接期 undefined
      _ZGIW3std
      —— 响亮的失败,不是静默错误。
    2. 纯 C 链接单元用 C++ 驱动 —— 这才是 libstdc++.so.6 的来源。修法要么让
      纯 C 单元改用 C 驱动,要么引入 --as-needed(后者会影响 dlopen 的库,
      需要单独评估)。

    建议按 2 另开一条,把本 issue 的判据收窄到 1。

    分析全文:.agents/docs/2026-08-15-issues-412-422-analysis.md

  3. added 2 commits that reference this issue on Aug 15, 2026
  4. speak-agent commented on Aug 31, 2026

    @speak-agent
    MemberAuthor

    症状已消除,分两步落地 —— 关闭得晚了,补记在此。

    归因的修正

    Sunrisepeak 的复现是对的,本 issue 的因果不成立:std.o 有 0 个未定义符号,它拖不动任何共享库。纯 C 包上的 NEEDED libstdc++.so.6 来自链接驱动 —— cxx_shared / cxx_link 一律用 g++,与链接输入里有没有 std.o 无关。

    症状是真的,归因是错的。这条 issue 因此被拆成两半。

    落地的两半

    本 issue(std.o 按需链接) —— 2026.8.15.1(commit c459cf2)。src/build/ninja_backend.cppm:2001:

    const bool takesStd = cxxUnit && has_std_artifacts && unit_needs_std(lu);

    has_std_artifacts 只回答「这个工具链有没有预建的 std 模块」,与单元自身的需求无关;unit_needs_std 才是关于这个链接单元的谓词,并且按 issue 要求覆盖了传递可达性,不只看本单元源码。回归测试 tests/e2e/235_std_object_only_when_needed.sh。

    后续在同一处发现 std.compat.o 完全没有 unit_needs_std 收窄 —— 本 issue 修了 std.o 而把它旁边那个漏了,于是一个 C++ TU 的全局初始化器进了每一个「工具链恰好带 std.compat」的链接单元。同一谓词、同一理由,已一并收拢,注释就在那两行上。

    驱动那一半 —— 拆出 #426,已修复关闭。非 C++ 的链接单元现在走 c_link / c_shared。

    判据

    issue 写的三条都成立:纯 C 包产出的 .so 的 readelf -d 里没有 libstdc++.so.6;import std 的工程正常链接运行;间接经依赖的模块接口用到 std 的工程也正常 —— 最后一条就是 235 里的传递性用例。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions