PAYATHON 2026

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 的精确系统修订号。需要根据运行时内核行为作出判断时,仍应使用运行时检测或针对具体功能进行探测。

备注:内容仅供参考。