公司有个运行在外网的生产环境,是 linux 早期版本,本着只要代码能运行,就没人去动的原则,内核一直比较老。

然后我想加一点小功能,想着现在 clang 那么强大了,跨平台编译应该轻轻松松,然而测试结果让我大跌眼镜。

目前几乎所有的 clang 交叉编译环境,都是基于新版本的 GLIBC ,相当于 VC 的 Runtime Lib ,而这货竟然严重依赖于系统内核版本?测试了好几个打包编译器,都是顺利生成 ELF ,可运行报错,提示让先升级内核。

无奈,一点点换老款编译器,从 2022 年的 clang ,到 2020 年的 clang ,到 2012 年的 gcc, 再到 2007 年的 gcc ,最后一个终于运行成功!

硬着头皮,用最原始的编译器,写最炫酷的 2023 新代码。

补一张错误图吧,是静态链接,也就一个 hello world ,运行就报"kernel too old"。

很多人没遇见过 GLIBC 内核不兼容问题。

路过,向前兼容?那个不是 JAVA 的 Slogan 之一吗——JAVA8 永远的神……

这方面无敌的还是 M$

c++ 一大特点
生产环境与编译环境绑定..

是向后兼容,不停的换编译器,脑袋都快裂开了。

内心非常不愿意用老款 GCC ,又不得不用的那种感觉,不知道怎么形容。

glibc 按理来说不依赖内核吧?不过只能新版本 glibc 兼容旧版本编译的程序,不能反过来。
MSVC 这边,之前是只能同版本兼容,但是能多版本共存。VS2015 开始可以跨版本兼容,但是如果程序使用了新版本加入的函数那就不兼容旧版本。不过 MSVC 一直可以静态链接。

1, C++11 之前的 C++ 真的忍不了;
2, clang 这种有很多默认检查,但基本可以关,这甚至直接导致 github 上一大堆开源代码拉下来编不过;
3, 一般 gcc 都是谈版本不谈年份。。。交叉编译,难道是嵌入式? linaro 最新的才 7.5...arm 官网有 9.x 的,不过 2012 年的 gcc 不能用是什么鬼

2023 年了为什么 c++ 还没有 npm, maven 之类的包管理器

glibc 是内核向用户态暴露的接口, 肯定依赖内核啊

依赖的,你 google 搜一下"glibc kernel compatibility",遇到这种问题,完全无解。

好像可以通过修改 GLIBC 里的 MIN_KERNEL_SUPPORTED 来屏蔽一些功能,支持老内核的 linux ,可这货一般没人去自己编译。

而且 GLIBC 和 VC Runtime 不一样,加了太多东西,静态编译会有各种奇怪问题,真是心力交瘁。

想要不依赖系统提供的库,不想用内核的 API ,就自己整齐一套
又想调用系统库,又要调用系统 API ,还想不依赖版本,这种好事哪有这么容易

自己把东西整齐活了,就不依赖了

是吗?不是 C 运行库吗?按理来说应该不太依赖系统功能的。

那我就不知道了,我只遇到过新 glibc 版本编译的程序拿到旧系统里运行不了的情况。

最近在搞公司项目一个老模块依赖的 openssl 升级,体会到了这种兼容问题,整得我狗脑子要炸了

#7 vcpkg ,巨硬的,我一直在用

試過靜態編譯?
其實是你 toolchains 有問題

用 Go 把,静态依赖编译,让你不用考虑环境的问题

如果依赖不多编译不麻烦的话, 可以尝试一下静态链接 musl(替换 glibc) 和 libc++(替换 libstdc++)

称为「 C 运行库」是因为 libc 会提供大量接口给用户态程序, 比如说 open 等等, 实际上底层就是将内核的 syscall 进行封装并暴露 abi 接口. 并且程序也是可以不依赖「 C 运行库」的, 比如 linux 内核.

静态编译后运行,运行也报内核太低,这点就很迷了。

toolchains 应该没问题,因为能正常生成 ELF ,我想在放到普通桌面 linux 上,大概率也能顺利运行。只是服务器比较特殊,不敢随便动内核。

想想还是 MS 好啊,静态编译 CRT ,基本上就没什么负担了。

#15 Go 也有内核版本依赖,1.18 已经去掉了很多对很多老内核的系统调用兼容。

你只是 ABI 相容,不代表 sysycall 相容
你大過是 timestamp 之類轉換為 64bits 這種 ioctl 出問題了

有一说一上面提到的问题都可以解决,看看 vc-ltl 那个项目,xp 到最新的 win11 ,就算 api 有变化,一样能兼容。从原理上来说 linux 下也能做到,只是没人做

正常的 c++ 只有代码能跨平台

跨 DLL 请自觉 extern C 或者 COM

编译器的锅关我 C++ 什么事?:doge:

我用 gcc 编译过很多代码,从来没遇到过内核版本的问题,无论是 x86 项目,还是 arm 项目,都很顺利啊。OP 文中说的“交叉编译环境”,一般指在 x86 下编译 arm 的环境,这个你得用“交叉编译器”,还要配置好环境变量,指定到你这个交叉编译器

交叉编译器一般使用 gcc ,除了 NDK ,我还没见过有什么项目使用 clang 作为交叉编译器

CLANG 比想的要更强,我就是编译 NDK 时发现,改改 taget 就能跨平台编译,就让人感觉很舒适。

最近 CLang 又加了对 VS IDE 编译和调试支持,生成的 OBJ 可以无缝嵌入 VC2015-VC2022 编译器。于是,我就想 ALl in one 一把,让 CLANG 也支持生成 Linux ELF 。

结果还是太傻太天真了。

正因为是运行时库,才依赖内核
C“语言本身”有啥可用库的……

没有什么是不能通过增加一层中间层来解决的问题,这个中间层可以理解为转换层、适配层。
具体技术可以是 函数指针,虚函数表,dlopen 等等,可以参考 COM 组件对象模型。

目前跨平台项目编译问题我还是推荐使用 conan ,对于三方库的管理和编译工具链没有重度依赖。

我们现在工程 C++ 标准锁定在 14 ,Windows > MSVC15 (如果没有用一些较新的 API ,使用 7.0 SDK XP 都可以支持)、macOS 、iOS apple-clang > 12.4 ( macOS ARM64 支持) 以上、Android NDK21 clang9 以上、Linux GCC5 以上。GCC5 已经支持大部分 C++14 标准,如果是为了兼容一些 GCC5 没有支持的特性或者旧内核系统,可以考虑 GCC7+ glibc2.23 的 docker 镜像,这样 Ubuntu 16.04 都可以跑。

主流的 libevent 、openssl 、sqlite3 、libcurl 、zlib 、qt 等都验证编译通过。

包管理 vcpkg 、conan ,国内的 xmake 也可以试试。只是还是一言难尽。。

要查清楚是谁报内核太低这个错,就是谁的锅

历史问题,如果依赖库的工程用的全是 cmake 的话,FetchContent 也是可以一把梭的

哈哈哈哈,跟我遇到的坑一模一样。
当然,新版 gcc 还有更坑的……我之前用 elf 编辑绕过了一下(就是去掉签名里面的 @版本),结果新版 gcc 加了几个 @@版本开头的隐藏函数( elf 编辑器里面根本找不着)直接干死了我试图绕过这个问题的方法……

glibc 不能够静态链接的,换 musl 就行了

这方面还是微软兼容性好,ntdll 从 xp 到 11 ,基本没什么问题

微软 nt 就很容易,xp 到 11 ,兼容性杠杠的

你遇到的大概是安全检查。如果没这个拦着你,等 syscall 行为不符合预期的时候,出 bug 你都不知道哪里死的。

怪就怪 Linux 内核 syscall 一直在升级 ( doge

glibc 的兼容性,和 C++ 动态库的兼容性是两件事,而且这两点几乎都和 clang 没什么关系。你提到的这些问题,大部分锅是 glibc 的。

glibc 几乎很少有人需要最新版的特性,所以只要你去链最老的 glibc 就可以了,一般会推荐让你到所有目标平台中最老的那个去编译。glibc 就是唯一特殊的那个崽,不能静态链接。

c++ 你想用新的编译器、新的库(包括 std )是完全可以的,既可以静态链接(如果你能搞到静态库),也可以自己打包动态库(比如 /usr/lib/myapp/libxxx.so )然后设置 rpath 到 $ORIGIN 之类的,后者其实就是 Windows 那种一个 exe 带一堆 dll ( vcruntime140.dll, etc) 的风格。

这两点综合起来就是,比如你需要给 Ubuntu 18.04, 20.04, ... 这些平台提供支持,那你首先准备一台 Ubuntu18.04 的环境,然后通过 toolchain ppa 之类的东西安装或者编译一套新版的 g++/clang ,多新的都可以,只要你能在 18.04 上跑。最后用这套工具链去编译你的 app ,然后把所有依赖 (除了 glibc) 通过静态或者动态的东西打包带走。

这个你找台环境试一下就知道怎么回事了,比如尝试一下给一台默认 gcc 5.x 的环境编一个使用了 C++17 Filesystem 的应用。

你要是觉得麻烦就直接 docker 得了。

runtime 也是要装一堆的,还有 .net 也是 n 个版本

我那公司跑还是原生跑,不过特意准备了好几套不同的 linux 环境的容器然后在 docker 里面编译,配合 jenkins 分发到对应的服务器,都是当初运维偷懒没有升级,最后谁也不敢动,结果各种版本都有。

glibc 有点像 windows 的 kernelXX.dll ,封装了很多 syscall 相关的东西。

然后有一种 docker 镜像叫做 manylinux 镜像,目的就是为可移植的 二进制编译 提供帮助。

github.com/pypa/manylinux

虽然主要用于 python 社区的二进制分发,但 python 你懂的,一堆 C/C++ 扩展,所以 C++ 的工具链是齐的。

没听过 java 还有这么一说 从来都是 跨平台 还是回到讨论 cpp 上吧

stackoverflow.com/a/11207190
看看这个? gcc 有选项可以指定兼容的内核版本的

#43 sorry 我好像搞错了,这里说的是编译 glibc 时的 configure 选项,需要你自己重编译 glibc 才行

嗯,我也看到了。查过的几个别人编译好的 gcc/clang 参数,基本都是默认值,没人去主动修改。

也许自己编译 GLIBC 是可行的,只是我没有尝试。

还有一点我帖子里没提到,实验下来编译 i386 ELF 是不限制内核版本的,只有 x64 位的 ELF 才限制。

项目所有代码在目标机器上源码编译,不就可以了。 除非有些库你没有重新编译

还能 golang 有远见,绕过 glibc ,直接 syscall

这种场景就是 golang 最大的优势之一

zig 内建的 clang 可以随意指定 glibc 的版本,就当作 abi 三元组的一部分
-target x86_64-linux-gnu.2.34 就能链接到 glibc 2.34 的版本(其他版本以此类推,最低到 2.16 ,十年前的版本),而且不在乎编译平台的 glibc 版本,甚至 windows 上也可以生成 linux 的 binary
参考 andrewkelley.me/post/zig-cc-powerful-drop-in-replacement-gcc-clang.html