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

9 天前
 w568w

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

  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 呢,这样分发软件不是方便得多吗?

3286 次点击
所在节点    C++
31 条回复
GeruzoniAnsasu
9 天前
一句话杀死静态链接:

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

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

但大多数程序都不是。

--------

另外

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

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


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

至于 C 标准库,我觉得 glibc 向下兼容做的已经相当好了。开一个低版本的容器,静态链接除了 libc 之外的库,随便复制到其他机器上运行。
qieqie
9 天前
就算标准库内置了,在发布的时候不也得搞定其它库的依赖吗,除非你依赖的第三方都是源码形式的库。
还是写一个 release 的时候 ldd 然后打包依赖的脚本吧,最后 LD_LIBRARY_PATH 一下。
macha
9 天前
windows 可以静态链接,编译时候选 MT 就行了。
我见过的产品都是静态链接的,否则每次部署之前都要替用户安装 runtime 。
cnbatch
9 天前
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
9 天前
MSVC 的运行时就是这样的呀,还可以选择动态链接和静态链接。
你说的不是 C++ 的问题而是 Linux gcc 的问题,gcc 一直拒绝支持静态链接运行时,同时用链接新版本 glibc 的程序不可在旧版本 glibc 环境下使用,即使程序完全没有用到新版独有的功能。
ysc3839
9 天前
@yanqiyu 其实 Windows 下静态链接了 CRT 之后,即使 DLL 是动态链接的,只要不跨 DLL 共享 C/C++ 数据,也不会有问题。
Linux 不能这么做是因为整个进程是共享符号表的,a.sob.so 不能有相同的符号。但 Windows 下 a.dll 和 b.dll 可以有相同的符号。
xyz1396
9 天前
@yanqiyu 我发现 gcc 版本在 11 以上用 cmake 静态链接 dlopen 的时候要报错,只有直接去设置 CMAKE_CXX_FLAGS 才能避开这个坑😂
bao3
9 天前
其实你的问题不是 C++ 自身引起的,是 Linux 这个大粪坑引起的,每个发行版都或多或少以自己的立场来做了一些改进,最终就是你面临的问题。
诚如楼列举的 BSD 的例子,也许 Linux 应该向 BSD 取经学习一下。
qq135449773
9 天前
其实我觉得对于一些场景来讲,容器化已经能解决很大一部分 glibc 这类的问题了...
Morxi
8 天前
怪,这么多楼居然没人提到 nix
直接把 /lib 里面的库给扬了,链接 /nix 下面包名+版本+哈希的 lib ,或者用一个 shell wrapper 软链指定版本的依赖到 /lib

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1141180

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX