macOS 是否有等同于 __FreeBSD_version 的版本宏?
结论
macOS 没有与 __FreeBSD_version 完全对应的整数宏,也没有能在预处理阶段表示当前运行系统精确版本或 build 的宏。
macOS SDK 定义了版本相关宏,用来表示编译时设置的最低部署版本,以及当前 SDK 支持的最高版本。这些值取决于编译环境,无法识别程序实际运行在哪个 macOS build 上。
编译期可以使用的版本宏
包含 <Availability.h> 后,可以使用以下宏:
#include <Availability.h>
__MAC_OS_X_VERSION_MIN_REQUIRED
__MAC_OS_X_VERSION_MAX_ALLOWED
__MAC_OS_X_VERSION_MIN_REQUIRED 对应 deployment target,例如 -mmacosx-version-min=10.13。__MAC_OS_X_VERSION_MAX_ALLOWED 对应当前 SDK 能提供的最高 macOS API 版本。
如果只想在最低部署目标不早于 macOS 10.13 时编译某段代码,可以这样写:
#include <Availability.h>
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
__MAC_OS_X_VERSION_MIN_REQUIRED >= 101300
/* 仅面向 macOS 10.13 及更高版本部署的代码 */
#endif
Clang 还会根据部署目标提供:
__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__
应用代码通常优先使用 <Availability.h> 中公开的 availability 宏。
这些宏描述的是二进制准备支持哪些系统版本,无法说明程序当前运行在哪个版本。例如,以 macOS 10.13 为最低部署目标编译的同一个程序,可以运行在多个更高版本上,但宏的值不会随运行环境改变。
运行时判断系统版本
如果代码行为取决于实际运行的 macOS 版本,可以使用 Clang 的 availability 检查:
if (__builtin_available(macOS 10.13, *)) {
/* macOS 10.13 或更高版本 */
} else {
/* 更早的版本 */
}
这项检查会生成运行时判断,可用于处理公开 API 的版本差异或选择兼容路径。它与 #if 不同,两个分支都必须能够通过编译。
如果需要获取 Darwin 内核版本或精确 build,仍需在运行时查询,例如:
uname -r
sw_vers -buildVersion
程序也可以通过 uname(3)、sysctl(3) 或系统版本 API 查询这些信息,从而省去 configure 步骤。不过,这些方法仍然不能提供编译期宏。
build version 为什么不能直接替代
sw_vers -buildVersion 返回的 "19H2026" 是 macOS build 标识符。它的开头部分与相应版本的 Darwin 主版本有关,但整个值并不是供 C 预处理器进行整数比较的版本号。
不能简单删除其中的字母,再按整数比较。build 标识符同时包含数字和字母,字母部分与后续数字都有版本排序含义。此外,macOS 产品版本、Darwin 版本和 build 标识符采用不同的编号体系。即使完成转换,编译期得到的也只是构建机器的版本,未必是目标机器的运行版本。
如果确实需要按 build 判断,只能自行定义解析和比较规则,并明确该规则仅覆盖项目实际支持的 build 格式。
如何处理系统行为变化
遇到 gettimeofday 这类底层行为差异时,需要先确认变化发生在哪一层:
- 公开 API 的差异,应使用
__builtin_available或 SDK availability 宏。 - 运行中内核的差异,应在运行时判断 Darwin 版本。
- SDK 头文件或编译环境的差异,应使用
__MAC_OS_X_VERSION_MIN_REQUIRED、__MAC_OS_X_VERSION_MAX_ALLOWED或功能检测。 - 如果代码直接依赖系统调用 ABI,应先查明行为变化对应的准确 Darwin 版本,不能凭经验把 macOS 10.13 等产品版本当作分界点。
假设已经确认相关行为从 macOS 10.13 开始变化,可以写成:
struct vki_timeval tv_now = { 0, 0 };
res = VG_(do_syscall2)(__NR_gettimeofday,
(UWord)&tv_now,
(UWord)NULL);
vg_assert(!sr_isError(res));
if (__builtin_available(macOS 10.13, *)) {
now = tv_now.tv_sec * 1000000ULL + tv_now.tv_usec;
} else {
now = sr_Res(res) * 1000000ULL + sr_ResHI(res);
}
使用这段代码前,必须通过源码、发布记录或实际测试确定行为变化的边界。availability 检查只能选择并执行指定的版本分支,无法证明底层系统调用的行为确实在该版本发生了变化。
如果项目不需要直接截获或模拟系统调用,建议优先调用 libc 提供的 gettimeofday()。与直接依赖 macOS 原始 syscall ABI 相比,公开库接口通常更便于处理跨版本兼容。
macOS 的 availability 宏可以代替一部分 configure 判断,但不能提供类似 __FreeBSD_version 的精确系统修订号。需要根据运行时内核行为作出判断时,仍应使用运行时检测或针对具体功能进行探测。
备注:内容仅供参考。
