似乎在 C 的领域,让一个新程序“为未来准备好”是一件很麻烦的事

1 天前
 jim9606

最近在修改一个 C 的开源小项目。因为是想在 Win 下用的,但项目是用 autoconf 构建的,所以准备了 MSYS2 UCRT64 环境来开发。然后就踩了处理不了大于 2G 文件的坑(用了 fstat)。虽然说就是一个 _FILE_OFFSET_BITS 宏的事,但毕竟是个跨平台项目,所以就查了下主流系统下这个问题的支持情况,感觉好多槽点。

  1. MSYS MinGW-w64 默认提供 32 位 off_t ,哪怕你在编译 64 位程序,想用需要上面的宏,但这并不是 POSIX 标准化的东西
  2. autoconf 有一个 AC_SYS_LARGEFILE 可以自动判断是否需要定义宏,但 autoscan 没提示需要这个
  3. 如果是 32 位 target ,似乎默认提供 32 位 off_t 是天经地义的
  4. 不想跟环境打哑谜的可以用明确 64 位的方法,例如 fseeko64 fstat64 ,但这玩意并不普遍可用,还是要构建系统检测。 例如老版本 android 就不支持。bionic 的文档说一大堆考量,结果就是想解决你得用较新的 ndk ,放弃支持旧版 android ,或者就放弃支持 32 位 abi 。(所以实际上哪怕是 arm64-v8a 的 android 系统依然有机会在处理大文件时出问题)
  5. 哪怕你不关心文件大小也可能被影响,例如查个文件创建日期就要用到 fstat ,尽管觉得做的事跟大小无关但就是有影响
  6. 如果在 abi 边界用了这些带 off_t 的变量/struct ,显然会出现 abi 不兼容问题,你也不知道一个第三方库可能用哪种配置

个人觉得 2GB 以上大文件并不是什么很罕见的用例,为什么开发环境就不能默认支持这些情况呢,难道还影响兼容性?

标题里的“为未来准备好”是指,只使用跨平台的 C/C++,加上 2000 年后的 POSIX:

  1. 已解决 2038 问题
  2. 除非明确要求,否则文本存储和 IO 都是 UTF-8 且不使用 wchar_t
  3. 无论环境如何都能正确在 ui 或控制台输出非 ASCII 字符
  4. 不拒绝使用 IPv6
  5. 支持长路径
  6. 保持对近十年出厂的系统和硬件的支持

似乎要在无第三方依赖的情况下做到这个是很困难的事?

2161 次点击
所在节点    C++
19 条回复
Thymolblue
1 天前
- 即使是 2026 年了,微软也没解决中文路径编码问题。我在程序里的字符串统一使用的是 UTF-8 编码,POSIX 下没有任何问题,但是在 Windows 下路径直接就识别不了。
- 另外跨平台项目更推荐使用 CMake 提供构建支持。autoconf 依赖 bash 环境,不是所有设备都能够很容易安装的。
- 现在很多开源的都库不支持编译成 32 位,32 位的系统也不常见了,不如直接只支持 64 位设备。
pursuer
1 天前
因为 C/C++跨平台本就不怎么样,autoconf 更是重量级,要跨平台就 libuv+CMake 。
确实 2GB 以上大文件不算罕见,但就像你说的,这是 ABI 不兼容,对于 c/c++,兼容性更重要。虽然 Glibc 自己都做不好 ABI 兼容。
xtreme1
1 天前
因为在 WINAPI / libc 层面是玩不了黑魔法的, 分分钟内存越界
capric
1 天前
你让 ai 把 autoconf 转换为 meson ,然后用 zig cc 当交叉编译器就很舒服
penisulaS
1 天前
见过一个项目直接用 rust 写壳包 c 核,就是因为 posix 兼容太烦人
changnet
1 天前
1. posix 是一套操作系统接口标准,而 C 是一门语言。写 C 不能按 posix 去写。就好比打包出来一个 apk ,老是有人问在 win 上为什么安装不了 apk 一样。
1. posix 在 linux 下还行,但在 win 下就是不行。这个是微软的问题,它早年就是独立于 posix 的。后来 posix 用得广了才勉为其难加了兼容接口,但一直不怎么维护,和 posix 的兼容性也不太好。
2. C/C++都是专注于语言而不是应用,它们所给的标准都是基于语言层面的。各个平台自己咋实现它不管。所以它的标准库接口很少,stl 也很少。很多功能必须用系统 api
3. minGW 和 CyWin 这东西本来就冷门,维护也是跟不上的。我试过把 linux 上的程序迁移到 win ,想着用 CyWin 可以少改些。但实际勉强能跑起来但很难维护,第三方库大多没有官方支持,所以直接切换到 MSCV 了。
4. C/C++现代一些的应该是用 CMake 的项目,autoconf 应该是 linux 下的项目了。

C/C++因为历史的原因,它就是没法做到像其他语言那样跨平台 api 统一的。如果是新项目,用 cmake + 新标准(比如 C++ 17 以上)可以解决 90%的问题,比如你说的用 C++的 std::filesystem 应该是可以的。剩下的 10%用各自原生的平台接口维护(比如网络接口)。

但你是旧项目,要么全改,要么写各种兼容
Mithril
1 天前
所以“新程序”已经尽量不用 C/C++来写了,特别是如果你的程序想要跨平台的话,项目做大了就会发现到处都是坑。

更别说你做个开源项目了,大半精力都要花在维护这些乱七八糟的编译环境上。
Mithril
1 天前
另外基于我之前做的调查结果,最省心最简单的 C++跨平台,就是你只针对 Windows 开发,然后各种*nix 全用 Wine/Proton 去跑。

Wine 上面的 API 兼容需要测试,你用到一些奇怪的 API 很可能在 Wine 上跑不通。不过但凡能跑通,绝对比你去每个平台维护一套编译,测试,要容易太多了。测试通过以后把 Windows 的二进制扔进去直接跑。

有人说 Wine 是各种 Linux 上最稳定的 ABI 不是没有道理的。各种发行版你要都支持,光是 glibc/Musl 都能搞你一下。

缺点就是这项目维护最好的只有游戏能用得到的那些 API ,如果你的程序还要调用别的 API ,比如硬件相关的,那就没办法了。
capric
1 天前
@penisulaS 我就是这样的,这样上层 rust 用 cargo-zigbuild 交叉编译,底层 c 也一起交叉编译
capric
1 天前
@capric 还把 rust 数据结构暴露给 c 用
Ketteiron
1 天前
Rust 会流行,有很大原因就是 C/C++ 这些狗屁倒灶的事情无法解决。
根本原因还是 ABI ,C/C++ 选择绝对的向后兼容,代价就是历史遗留问题几乎无法处理; Rust 非常激进走了另一条路,不提供任何稳定 ABI ,因此清理历史包袱的代价非常轻。但是,哈哈,Rust 有稳定的 C ABI 。
要我说,还是 C++ 太贪婪了,既想要一切抽象和范式,又想要保证向后兼容,还得为 n 个平台擦屁股,根本是做梦。
目前最务实的做法还是 Rust + C ,两个语言都有各自的破事,不如合起来只用最好的部分。
xuanbg
1 天前
直接让 AI 翻译成 rust 好了
WorseIsBetter
1 天前
我的个人项目(参考我发的第一个主题帖)也有处理 32-bit/64-bit 的 off_t/time_t 。我觉得这块其实是很 trivial 的,没你说的那么复杂。

原则只有一条:管好自己的项目就行。

你的代码里需要意识到这些类型有 32-bit 和 64-bit 两种可能,并且分别对其做支持(或者不对某些情况做支持并让构建过程失败),就足够了。至于和其他程序的 ABI 兼容,是由发行版的包维护者或者用户自己(如果他们选择自行构建)来保证的。

除此之外,你唯一能做的,大概只有把构建脚本写得更灵活、优雅,再配上清晰明确的构建文档,让包维护者和用户少走或不走弯路。

另外:不建议依靠 autoscan ,那玩意只起到一个基本的示例作用,对实际项目的帮助不大。还是得想清楚自己具体需要在 autoconf.ac 中检查什么、配置什么,然后根据需求来写
xiliuya
21 小时 17 分钟前
不要用 windows 就可以完美解决这个问题,不是 c/c++的问题,而是 win 的锅。
beautyplus
20 小时 45 分钟前
本质还是编码对不上,跟 windows 系统 **交互** 是要尽量用 wchar 的,尤其是带路径参数的函数比如 wfopen ,CreateFileW ,因为你不确定目标机器有没有开启 utf-8 (区域设置中的使用 utf-8 还标记着 beta 默认不勾选)
cnbatch
19 小时 21 分钟前
这是 C 的事情,发到 C++板块不怎么妥当吧

前半部分

1 ,这是 MSYS MinGW 的锅,它们没处理好 64 位的适配。Windows 提供了 fseeko64 的替代品 _fseeki64(),中间转换层本该做好映射支持的。
2~6 ,这就是系统 API / SDK 的锅,包括 Android

至于换语言能“解决”这些锅,本质上只是它们在标准库里面帮忙擦屁股而已。


「为什么开发环境就不能默认支持这些情况呢,难道还影响兼容性?」

开发环境当然可以默认支持这些情况,正常 POSIX 环境、正常 MSVC 环境直接写程序调用标准库 API 都没问题。
没做到默认支持,很多时候完全是疏忽 (MSYS MinGW),或者是怕麻烦、赶进度(Android 5.0~6.0)。


后半部分

1 、若是 OS Level 的限制,编程语言本身能做的事情其实很有限。如果 OS 本身没解决 2038 问题,只要系统关机、重新开机,系统时间直接就是乱的,再完善的编程语言都没办法强行掰回来。
若 OS Level 已经解决了 2038 问题,那么无论是 C 还是 C++,都不需要担心这种事。

2 、纯 POSIX 可以。跨平台跨到 MSYS MinGW 的话,按理说 MSYS MinGW 应当处理好相应转换(像是背后处理编码调用带 W 的 API 、检测目标 Windows 有没有启用 UTF-8 区域选项),但这就不是普通使用者可以强制要求的了。

3 、这就有点霸道了,别说 C 语言、C++,哪怕是 Python 都没法如此保证。

4 、单纯“不拒绝使用”的话,那还是不需要担心。如果想要更高要求达到“表现一致”,那就不太可能了。
Linux 的 IPV6_V6ONLY 默认为 false ,而 BSD 和 macOS 还有 Windows 的 IPV6_V6ONLY 默认是 true 。
OpenBSD 更是搞起了内部限制,无论 IPV6_V6ONLY 设成什么值,它都按照 true 处理。

5 、类似 2

6 、支持近十年出厂的系统很合理。但硬件??这可不是编程语言可以掌控的吧,这是 OS 和驱动程序的责任啊。
adoal
19 小时 9 分钟前
文件系统和其它需要 text label 的 API 支持 Unicode 用什么模式只是一种选择而已。Unix 世界为了兼容标准 C 库的字符串 0 结尾语义而选择了 UTF-8 locales ,Windows 为了不破坏已有的 MBCS locales 里的编码语义而选择了用双模 API ,本来就是不同理念,各有优缺点。没理由 POSIX 的做法比 Windows 更正确或更“标准”。
xyx0826
6 小时 8 分钟前
参见 [jart/cosmopolitan]( https://github.com/jart/cosmopolitan) 项目,一个让 C 语言一次编译、到处运行的库。现支持 Windows, Linux, macOS, *BSD 。UTF-8 读写支持也包含在内,例:[romanize.c]( https://github.com/jart/cosmopolitan/blob/master/examples/romanize.c)

这是我所知的最接近你要求的项目。
xyx0826
6 小时 3 分钟前
附:redbean 是同作者基于 Cosmopolitan C 开发的一个跨平台 web server, 本身还是一个 zip 包用来装 lua 脚本和静态资源。

https://redbean.dev/

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

https://www.v2ex.com/t/1204467

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

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

© 2021 V2EX