技术解析

不同版本的 Linux 编译的 c/c++程序是否通用
0
2021-06-15 13:56:37
idczone

一直有个疑问,因为今天推荐的 linux 帖子很多,自己也想试试其他系统,换个口味。

问题就是,公司的线上服务器是 debian,所以开发机也是 debian,c++程序。是否可以在其他版本的 linux 下开发编译,然后在 debian 的系统上运行。

或者退而求其次,在 debian 系的系统上开发编译,然后在 debian 的系统上运行。

不考虑配置编译服务器等情况,只是好奇这种跨 linux 的是否可以通用。比如 ubuntu/suse/arch 上编译的程序是否可以在 debian 上正常运行性。


可以
不过如果要调用本地的.so 的话回有点问题,

没有问题,因为都是 Linux 内核。自己稍微管理好一下依赖就行。

动态链接库会有问题。

不同版本的编译器可能存在 ABI 不兼容的问题

不一定,有很多因素可以导致差异,有些好解决,有些只能规避
比如前面提到的共享库版本问题,这个解决起来就是整一个容器或者最简单一个 chroot 环境,在里面编译就肯定兼容了
必须规避的就是由于 cpu 指令集的差异造成的问题,这个时候就得对着目标设备的 cpu 特性开编译选项了。。。

不同版本的 debian 估计都会有问题吧。更别提不同的发行版了。
玩玩都可以。工作就算了。

能跑是巧合。。。
linux 内核 abi 更新是一方面问题,
编译 c++库异常处理,可以用多种 c++abi,这个不一致,导致各种异常随机坑。
https://cloud.tencent.com/developer/article/1357674
https://maskray.me/blog/2020-12-12-c++-exception-handling-abi#%E4%B8%AD%E6%96%87%E7%89%88

弄成 appimage 就行

不同 libc 编译出来的都不一定不能用
某些接口如果变化了会出现 crash 或异常,如果没有,则能正常运行

动态库有差异的话比较麻烦
比如在 Fedora 23 上编译的程序,在 RHEL 8 上都不能直接运行,libc 不一样
当然你可以静态编译或者打包动态库进去



用 Musl 加静态链接,基本不会有问题

写简单的 C++肯定没问题,中等的会有各种小问题(比如楼上提到的 libc,so 问题),超大型项目的 Cross Compile 就算了吧。
老老实实装一个 debian gcc/clang,比什么都好。

内核版本也是有限制的,你 file 一下可执行文件就可以看到可运行这个程序的内核的最低版本,只不过这个版本一般都很低,发行版本通常都可以满足

不用动态链接库, 项目中没有太多外部依赖,应该问题不大
用了动态链接库, gcc 版本相同, 或者说 很多核心的.so 相同 问题应该也不大
主要写 go C++不太熟悉 可能说的不专业

musl

1 、如果你有本事重新编译一遍所有依赖库(并带上,就好像带了一堆 dll 的 windows 程序那样),或者静态链接依赖库,感觉内核版本差不多的话,哪个发行版都能随便用你编译出来的程序。
2 、你可以用 docker

问题不大

影响兼容性的原因大概有动态链接库、glibc 版本、内核版本等,不过很多时候都是能兼容的。
我的经验是,单位的超算是 Redhat,没有管理员权限,编译 C++程序经常缺少库,手动安装库又非常麻烦。我的一个办法就是在一台 centOS 虚拟机上编译好,上传到超算上面使用。
你的应该是生产环境不想安装那么多的开发库,开发环境编译好之后,直接运行在生产环境中,这个没有问题的。怕不兼容,尽量 静态链接 + 开发环境和生产环境使用同一个 linux 发行版。

系统调用(System Call Interface)是比较稳定的,也就是说你用到的系统调用大概率在十年前的内核上都有。这也是 docker 能好用的原因之一。
目前看来比较容易影响的是 glibc,因为它不推荐使用静态链接,主要涉及 libc.so.6 和 ld.so ,前者必须跟后者版本严格一致,后者通常通过 interpreter path 显式指定使用系统提供的版本。你也没法控制编译器不使用新版 glibc 新增的符号。
另外两个基础库 libstdc++.so 和 libgcc_s.so 倒是可以选择静态链接,问题不大。
如果能用 musl-staic 或者像 go 那样完全不依赖 glibc 也是很好移植的。
所以通常做可移植的做法是把所有用到的 so(包括 glibc)全打包到程序目录下,用 patchelf 修改 interpreter path 和 rpath 。

cpu 一样的话( x86-64 和 arm 不能混用)是可以的
最好 gcc 版本也一样

C++的话有一个要注意,gcc 的 C++库有两种 ABI,一种是 gcc4.x 版本普遍使用的旧 ABI,还有一种是 gcc5.0 版本引入的 C++11 ABI (主要是为了符合 C++11 规范,STL 中 string 和 list 的实现有修改;但是新版 GCC 也可能会使用旧版 ABI,要看具体发行版的情况),如果 C++ ABI 不兼容的库被链接在一起,编译出来的程序运行以后会 crash 。
另外也有的发行版启用了 dual ABI,默认是 C++11 ABI,如果有需要的话,可以用-D_GLIBCXX_USE_CXX11_ABI=0 就可以让编译出来的程序使用旧版 ABI 。

大部分都直接兼容的。glibc 貌似还能直接静态链接。然后就可以执行了

总的来说就是,第三方库跨版本,可能会有 API 或 ABI 兼容问题
如果不考虑第三方库版本的问题,那么 C 语言一般不会有 ABI 不兼容(虽然 C 语言没有官方规定的 ABI,但是作为操作系统基础语言,事实上具有较强的兼容性),但 C++可能会有 ABI 不兼容(确实存在不兼容的情况)

linux 平台的软件也有二进制发布形式的,不一定非要把源码拿到自己的电脑上重新编译。

就连高度统一的 Windows,在一个版本的 OS 上编译的程序,换个版本都有可能出问题,更何况 Linux 。
这个话题牵涉的东西太多了,可以讲好几天。
建议:
1.把主流的 Linux 版本,都编译一个版本出来,方便测试。
2.针对方便客户,甚至可以直接做一个配置好的虚拟化版本,方便客户测试。
3.如果客户的环境,和你们的预编译的所有版本都不一样,你可以建议客户,把他们的系统发给你们,可以是光盘、镜像文件、甚至整个虚拟机的打包。你们针对客户的系统版本,出个编译版本。

编译出来的程序用 ldd 工具查看一下动态链接的库,一般会受动态库的版本的影响。影响比较多的是 glibc 的版本,版本不一样就会导致跑不起来。
一般来说,ldd 之后,只要把所有动态链接的 .so 文件一并拷贝走,并在运行时用 LD_LIBRARY_PATH 替换掉系统的不兼容版本,大部分程序应该就能正常起来了。
所以很多二进制发布的程序,都会在压缩包里把所有动态库都带上,防止版本问题。
除非还涉及到 Linux 内核的一些选项。
另外,还要注意 CPU 架构不通用,x86 的程序不能在 arm 上跑。

我会用 Alpine 编个静态的可执行文件出来(编译环境直接写成 Dockerfile ),到处拿着用
手上的项目有几个用 CoreOS 跑的(它没有包管理器),虽说有 toolbox 但是一些常用的工具还是喜欢跑原生的,比如:tmux 、htop 、甚至 xtrabackup 。我就都用 Alpine 编译出来个静态执行文件,直接运行就好了

如果是应用程序 不要想了 直接 AppImage 否则就是自己折腾自己

如果是有动态调用,不要做任何能够兼容的假设,或者说期待。
这一点 C++就跟 go 差远了。

glibc 版本不同有时候会遇到吧

appimage 也是存在动态链接的

静态编译或者 docker?

大佬,我在 cgo 编译时就遇到了 glibc 版本不兼容的问题,编译环境是 gcc8,glibc2.28 ;运行环境 glibc 的版本是 2.27 就运行不了了,请问编译的时候可以指定 glibc 的版本吗?

推荐 zig 语言的编译器,对交叉编译支持很叼,原生支持编译 c 语言。
用它来编译 wasm,各种嵌入式平台,以及 freestanding 的很容易。
https://ziglang.org

看到这个问题就想到,同一个 exe 安装包能在 2001 年发布的 xp 上跑,又能在 2021 年的 win10 上跑,真是太强大了

- 动态库问题会比较多,尽量静态编译
- 即使静态编译,一般还会依赖一些动态库,比如 glibc,不同版本有兼容性问题

file 一下你会发现一个 target kernel version 2.6.25 居多,这个东西怎么来的我前不久刚好研究过,似乎跟编译的工具链和编译环境的链接库都有关系

我觉得这个问题和 C++ 没关系, 应该是操作系统可执行文件的范畴
除非 java 一样有虚拟机支持, 否则直接运行在操作系统上的可执行文件, 都有类似的问题: ABI 兼容问题

想有便携性编译成纯静态的 elf 就行了,几乎只跟内核 ABI 有关系了,有误请大佬指出,另外这个版本跟 GCC 有关系吗,似乎从 vc 编译逻辑上讲会定义一个 WINNT_VER 的 marco,而这个值跟 VC 里的头文件有关系,或者 include windows SDK 的,不知道 Linux 又是什么关系,没有深究过

操作系统可执行文件都是 ELF 格式,楼主的这个问题算是运行时库的问题

Linux 系统调用看 11L 。
glibc:
https://abi-laboratory.pro/?view=timeline&l=glibc
libstdc++ 在 GCC 5 以后是明确有 ABI brakage 的(为了 C++11 conformance )。比起 MSVC 已经靠谱得多了,但考虑到 Linux 上 C++ 运行时基本是当系统库部署的,基本不会打包(除非 nix/guix 那种)。但如果你已经会自己对付这种部署问题,就问不出来问通用不通用的问题了。所以凉拌。
还有库的各种 ABI bug 得看具体版本。

操作有点复杂,参考这个 ( https://tldp.org/HOWTO/Glibc2-HOWTO-6.html )
通过 -nostdinc -nostdlib 等开关禁止自动引入基础库,然后手动加入旧版 glibc 的包含目录和链接库。
一般还是用 docker 或者 chroot 解决比较省事

开发机器开发完,拿到运行机器上编译一个不就行了。或者准备一台和运行机器一样软件环境的编译机。Linux 软件分发不都是源代码级别的吗?

你也不看看你电脑上装了多少个版本的 C++ 运行库……

Win Vista/7/10 添加的 Win32API 实在太多了,一个现在开发的程序跑不起来不是什么奇怪的事情。例如 Chrome 这些就不支持 XP 。

小心选择交叉编译环境就行了。重点在于 glibc 、内核及 gcc 版本。。。
我的 SG 项目编译后基本就是“系统+cpu 架构”,也就是 linux-x86_64 通用、linux-arm 通用等。。。
github.com/lazy-luo/smarGate

可行

多数情况下可以通用。
需要注意的就是,CPU 架构要一样,且调用的系统 API 两边都有,最后就是依赖的动态链接库两边都有。

数据地带为您的网站提供全球顶级IDC资源
在线咨询
专属客服