C 编程语言由一套标准定义,这些标准规定了代码在不同系统上的行为。然而,这些正式规范与使用 GCC 和 Clang 等主流编译器的实际情况之间存在显著差距。这种差异之所以出现,是因为编译器往往优先考虑性能、向后兼容性或特定的平台行为,而非严格遵守最新的标准。对于许多开发者来说,“现实世界”中的 C 语言是由这些工具的行为定义的,而非标准本身的文本。
几个具体的技术不一致突显了这种分歧。例如,`asm` 关键字在标准 C 中是一个有效的非保留标识符,但 GCC 和 Clang 默认将其视为扩展。这意味着技术上符合标准的代码,除非使用特定的标志,否则可能无法编译。同样,标准规定了五个用于控制浮点运算的 pragma(例如 `STDC FP_CONTACT` 和 `STDC FENV_ACCESS`)。尽管这些 pragma 在 27 年前就已成为 C99 标准的一部分,但 GCC 仍将其视为无操作(no-ops),意味着它们对编译器的行为没有任何影响。虽然 Clang 对某些 C99 pragma 提供部分支持,但它并不支持更新的 C23 pragma。
其他差异涉及编译器和库如何处理特定于环境的细节。在 `FLT_ROUNDS` 的情况下,标准要求该值根据运行时的浮点环境动态变化。然而,GCC 将 `FLT_ROUNDS` 定义为常量(1),无论运行状态如何。此外,`musl` C 库提供了自己的 `stdarg.h` 版本,这可能会与编译器提供的头文件发生冲突;具体来说,它对 `va_start` 的定义与 C23 标准不兼容,因为 C23 标准允许省略第二个参数。这些不一致性表明,虽然标准提供了一个理论框架,但 C 语言的实际执行受到开发者选择的特定工具链和库的严重影响。
最终,这些偏差为可移植性创造了一个复杂的局面。虽然标准旨在确保代码在不同机器上表现一致,但现实情况是,程序的行为往往与编译器的特定特性以及底层系统库紧密相关。对于许多现代开发者来说,“标准”C 与“实用”C 之间的区别,是在理论一致性与最广泛使用的工具提供的优化、平台特定功能之间的权衡。
