为什么 C/C++ 语言的标准库不做成 Java 那样可安装的运行时? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
w568w
V2EX    C++

为什么 C/C++ 语言的标准库不做成 Java 那样可安装的运行时?

  •  
  •   w568w
    w568w 105 天前 4490 次点击
    这是一个创建于 105 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题。现在的平台/编程语言运行时,按兼容性大致可以分成几类:

    1. 几乎不变动,程序可以动态链接/调用:POSIX API ,Win32 API ;
    2. 经常变动,但可以自由安装:Python ,Java ,NodeJS ,C# .NET……;
    3. 经常变动,但直接嵌入到编译后的程序:Go ,Rust……。

    唯独 C 和 C++,在 Linux 下不仅经常变动,并且不向前兼容(即旧版 libc(++) 不能运行新版 libc(++) 的程序)、不互相兼容( musl-libc 的程序不能用 glibc 运行),我在 Arch 上编译的程序几乎没法拿到 Ubuntu LTS 上运行,只能开个 Docker 容器来编译。

    这就导致两个奇怪的现象:

    1. 在面向旧发行版开发 C/C++ 的时候,很多人宁愿自己搭古老开发环境来编译,也不愿/不能升级目标环境。而在 Python 、Java 之类的语言里,第一反应是直接给目标环境装个新版 Python/JRE 。
    2. 很多 C/C++ 软件不得不发布一堆变体来保证兼容性(此外,C++ 还有 CXX11ABI 的兼容性问题),或者干脆让用户自己编译(然而用户并不是何时都有空、有能力搭环境,尤其是一些编译依赖非常复杂的软件)。

    但似乎很少有人会专门去下载标准库实现。除了 Conda ,好像也没人关心怎么打包标准库。

    为什么 C/C++ 不像 JRE 那样,发布一个 C/C++ Runtime 呢,这样分发软件不是方便得多吗?

    31 条回复    2025-06-27 15:24:54 +08:00
    InkStone
        1
    InkStone  
       105 天前
    C/C++也可以像 Go 和 Rust 一样静态链接 Runtime 的啊,只是习惯上不那么做而已。
    w568w
        2
    w568w  
    OP
       105 天前
    @InkStone 是的,但对于静态编译,glibc 是 strongly discouraged ,musl libc 也会有各种问题(例如不兼容 glibc 的一些代码、部分实现性能低),其他 libc 就更不用说了。

    至于 libc++/libstdc++,我似乎都没看到有人静态编译过……
    hwdq0012
        3
    hwdq0012  
       105 天前   1
    linus 好像有要推一款 linux ,全静态链接的,就是为了解决 linux abi 兼容不好的问题
    yanqiyu
        4
    yanqiyu  
       105 天前   1
    静态链接 libstdc++/libc++我觉得很正常啊,只是这时候要保证引用到/会 dlopen 的的所有动态链接库别再拉一个 libstdc++/libc++进来就行。

    并且向后兼容相对容易,给符号标记上版本然后保证老版本符号永远不会被扔掉就行,但是向前兼容就基本上意味着不能加任何新的符号(因为老版本没有),除非这个语言/运行时永远不演化,不然向前兼容不现实

    (其实你可以 LD_LIBRARY_PATH 塞一个自己编译的新版本的库来让程序能跑起来的,科学计算集群全员 CentOS7 的时候我经常这么干)

    考虑到和 rust 的区别,rust 你编译什么东西默认都是静态链接,但是你要是真的想要用 rust 做整个操作系统的 userspace ,大范围用上了动态链接(节约磁盘之类的理由),到时候 C++遇到的 ABI 的鸡毛蒜皮一个也跑不掉。

    或者说你要是能接受编译 C/C++项目的时候像编译 rust/go 项目一样把整个项目的所有依赖全部编译一遍然后静态链接,那 ABI 也不是问题。大家不习惯的原因大概是 C++生态出现在集中的包管理之前,没有包管理、和系统里面依赖 C++的东西太多了,以至于用系统的库已经成了最经济的选择了吧。
    yanqiyu
        5
    yanqiyu  
       105 天前   2
    @hwdq0012 这个取舍,就是哪天 libc 出现 CVE 你就会惊讶地发现所有软件都弹出来了安全更新。
    greygoo
        6
    greygoo  
       105 天前   1
    生态问题,C++不缺少一个新 C/C++ Runtime,但是别人会不会用又是另一个问题,本身大家都是各玩各的.其实 docker 也是挺好的一个解决方案.
    cwcc
        7
    cwcc  
       105 天前   2
    古老的代价,有很多库在现在的操作系统中仍然依赖,但它们已经没有任何官方维护了,取而代之的是一堆各种第三方、高校、企业给老库打的 patch 。久而久之形成了一个非常难受的依赖。

    另外对底层兼容也要花费大量心思,更何况静态编译。Linux 上自己都不统一:glibc 对静态链接不友好、有发行版不依赖 glibc 、glibc 版本依赖问题等等。

    > 这我就要夹带我的私货了:全静态构建的 PHP 运行时( https://static-php.dev )中 linux 的部分我就是花了很大的精力去 patch 各种依赖库从而实现构建独立的二进制,甚至专门编译了两套专门的工具链。还有一套另一位贡献者在研究接入 zig 。
    pursuer
        8
    pursuer  
       105 天前   1
    我觉得这不能完全算是 C/C++的锅,Windows 的 COM 组件也是 C++的,但 Windows 的兼容是公认做的比较好的。再看 Linux 阵营,虽然很多库是用 C 写的,但升起来也费劲,glibc 更是重量级。
    w568w
        9
    w568w  
    OP
       105 天前
    @yanqiyu

    > 想要用 rust 做整个操作系统的 userspace ,大范围用上了动态链接(节约磁盘之类的理由),到时候 C++遇到的 ABI 的鸡毛蒜皮一个也跑不掉

    我觉得比较好的形式可能是 Windows 的 Visual C++ Distributable 那样,把标准库按年代打包成运行时,然后让用户安装。这样既可以保证不需要向前兼容,又能兼容较新的程序,还能节省硬盘空间。
    yanqiyu
        10
    yanqiyu  
       105 天前
    @w568w #9 还是会绕不开统一分发的问题,比如 A 程序依赖 B/C 两个库,但是 B 是编译链接到 2024 版本的 stdlib 但是 C 是链接到 2025 版本的 stdlib ,这么下来依旧会出问题

    结果就是整套动态库都得有多个版本。
    xtreme1
        11
    xtreme1  
       105 天前
    因为一旦错失了时间窗口, 再想统一已经不现实了
    unused
        12
    unused  
       105 天前
    没见过 VC++ runtime redist ? C/C++ 生态是碎片化的,不像另外几个话语权完全在一两个组织手里。标准库用什么形式分发都可以,只是大家习惯了现在主流的方式。
    unused
        13
    unused  
       105 天前
    另外你说的另外几种语言,运行时基本也都依赖 libc 的,有人帮它们负重前行。
    w568w
        14
    w568w  
    OP
       105 天前
    @pursuer @unused #12 我也越来越觉得这个更是 Linux 的问题了。就像包管理和 init 的选择,永恒的碎片化……

    @unused #13 是的,但作为最终用户和开发者其实都不用考虑这些,相当于有人做兼容了。而且只要有一个人做好兼容,其他用户都能获益。
    fcten
        15
    fcten  
       105 天前
    打包运行时只是方便了开发者。对于用户来说可是深恶痛绝。谁也不想自己电脑里同时跑十几个不同版本的运行时把?
    w568w
        16
    w568w  
    OP
       105 天前
    @fcten 比现状好吧,现状是「只允许同时用一个运行时,兼容性问题用户自己想办法」。在「让用户装多个运行时」和「让用户自己学习编译知识从头编译,或者为了你的软件升级整个系统」,我觉得还是前者方便点。
    bli22ard
        17
    bli22ard  
       105 天前
    C/C++ 静态链接 musl-libc 和其他依赖, 不就达到 0 依赖了? java 不会向前兼容, 老版本的 jre ,不能运行新版本 jdk 编译的 class
    w568w
        18
    w568w  
    OP
       105 天前
    @bli22ard #17 理论上是这样,但实际上有很多依赖根本无法静态链接(作者写 Makefile 就没考虑到,比如 GNUStep ),或者依赖极其繁杂(例如 libcurl ),或者使用了 musl-libc 不支持的扩展(例如 onnx )。感觉一切的坏头都是 glibc 起的……
    bli22ard
        19
    bli22ard  
       105 天前
    @w568w #18 所以 linux 上的软件分发要依靠包管理器, 你要发行到哪个发行版的具体版本都是一定的。不然动态链接的软件,基本等于不能用。这依赖确实挺烦的。我搞 rust , 就遇到过动态链接,要访问 https api ,依赖 openssl 搞的编译焦头烂额
    julyclyde
        20
    julyclyde  
       105 天前
    libstdc++不就是可安装的吗?
    GeruzoniAnsasu
        21
    GeruzoniAnsasu  
       105 天前
    一句话杀死静态链接:

    程序依赖 libc->ld.so->vdso->内核。

    你想要 c 程序完全静态链接,那么你得想个办法永远不依赖内核 dyanamic mapping 出来的任何东西,那么你只能「真的只使用 syscall 封装」来写程序,which busybox 几乎就是这么做的。

    但大多数程序都不是。

    --------

    另外

    > 宁愿自己搭古老开发环境来编译,也不愿/不能升级目标环境

    不知道你有没有注意到,在新系统上搭旧环境其实相对容易的,也能做得到独立一个运行时,toolchain 设个 sysroot 就够了,但旧系统上搭新环境尤其难,就是因为新环境动态依赖的新特性涉及一堆最后要指望内核提供的能力,所以几乎没法升。


    GNU 有点像所有 project 都在维护同一个大系统,你要么在「写插件」,要么在「写宿主」新插件没法在旧架构上跑,你发布一个「新环境」就意味着「宿主系统」升到了一个新版本……内核+runtime+版本都一起升了。
    mahaoqu
        22
    mahaoqu  
       105 天前 via iPhone
    Windows 上 C++标准库是跟随 VS 发布的,所以数量很少。Linux 的实现就太多了,要发布运行时发行版就要发布 libstdc++-12.1 ,libstdc++-12.2 等等,得几十个不同的小版本。对于开源软件重新编译就完了,二进制兼容完全没意义。对于复杂的应用程序,flatpak 这样的应用容器才是最好的办法。

    至于 C 标准库,我觉得 glibc 向下兼容做的已经相当好了。开一个低版本的容器,静态链接除了 libc 之外的库,随便复制到其他机器上运行。
    qieqie
        23
    qieqie  
       105 天前
    就算标准库内置了,在发布的时候不也得搞定其它库的依赖吗,除非你依赖的第三方都是源码形式的库。
    还是写一个 release 的时候 ldd 然后打包依赖的脚本吧,最后 LD_LIBRARY_PATH 一下。
    macha
        24
    macha  
       105 天前
    windows 可以静态链接,编译时候选 MT 就行了。
    我见过的产品都是静态链接的,否则每次部署之前都要替用户安装 runtime 。
    cnbatch
        25
    cnbatch  
       105 天前   1
    Windows 就是这样做的,自 Visual Sutdio 2005 起,每个 VS 版本都自带一个 VC++ Runtime Redistributable (直到 VS2015 为止,后续全部使用单一可升级的 Runtime ),方便是方便了,缺点也是曾经存在过的:

    早期有些工业软件和游戏在分发的时候顺便安装自己用着的 VC++ Runtime ,恰好电脑上另一个软件也用了同样大版本的 runtime ,但小版本号不同(已经给系统安装了新一点的版本),于是 runtime 就卡住安装不上,导致后续安装过程无法继续。

    其实在同一个大版本的新 runtime 存在时,手动安装旧 runtime 是会提示 runtime 已经存在,不需要安装。然而那些软件/游戏开发商当时懒得理判断。过了几年 runtime 版本越来越多,他们才逐渐明白安装前需要做判断。

    特别是自 VS2015 起,runtime 都是共用的,只不过新版本会覆盖旧版,开发商们必须判断 runtime 版本该不该安装。自此以后,无论是开发商还是用户,都不再需要受到 runtime 版本的困扰了。

    ------

    其实自带 runtime 还有另一种方式:BSD 模式,或 Alpine Linux 模式

    各大 BSD 的 libc 都如同 Alpine Linux 的 musl 那样,既能动态链接,也能静态链接(把 runtime 打包进最终程序)

    在高版本 BSD 编译出来的 Native 程序,如果没有调用到高版本系统特有的 API ,那么静态链接后是可以在低版本 BSD 系统当中使用的。这一点跟 Windows 差不多。
    ysc3839
        26
    ysc3839  
       105 天前   1
    MSVC 的运行时就是这样的呀,还可以选择动态链接和静态链接。
    你说的不是 C++ 的问题而是 Linux gcc 的问题,gcc 一直拒绝支持静态链接运行时,同时用链接新版本 glibc 的程序不可在旧版本 glibc 环境下使用,即使程序完全没有用到新版独有的功能。
    ysc3839
        27
    ysc3839  
       105 天前
    @yanqiyu 其实 Windows 下静态链接了 CRT 之后,即使 DLL 是动态链接的,只要不跨 DLL 共享 C/C++ 数据,也不会有问题。
    Linux 不能这么做是因为整个进程是共享符号表的,a.sob.so 不能有相同的符号。但 Windows 下 a.dll 和 b.dll 可以有相同的符号。
    xyz1396
        28
    xyz1396  
       105 天前
    @yanqiyu 我发现 gcc 版本在 11 以上用 cmake 静态链接 dlopen 的时候要报错,只有直接去设置 CMAKE_CXX_FLAGS 才能避开这个坑
    bao3
        29
    bao3  
       105 天前   1
    其实你的问题不是 C++ 自身引起的,是 Linux 这个大粪坑引起的,每个发行版都或多或少以自己的立场来做了一些改进,最终就是你面临的问题。诚如楼列举的 BSD 的例子,也许 Linux 应该向 BSD 取经学习一下。
    qq135449773
        30
    qq135449773  
       104 天前
    其实我觉得对于一些场景来讲,容器化已经能解决很大一部分 glibc 这类的问题了...
    Morxi
        31
    Morxi  
       104 天前
    怪,这么多楼居然没人提到 nix
    直接把 /lib 里面的库给扬了,链接 /nix 下面包名+版本+哈希的 lib ,或者用一个 shell wrapper 软链指定版本的依赖到 /lib
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1082 人在线   最高记录 6679 &nbp;     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 23:14 PVG 07:14 LAX 16:14 JFK 19:14
    Do have faith in what you're doing.
    ubao 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