10. 与标准的差异(ISO C90 / C99)
本章是本文档的符合性声明部分。判定基准为 ISO/IEC 9899:1990(C90) 与 ISO/IEC 9899:1999(C99),判定对象为本文档所描述的交付库。
节 内容 10.1 支持标准、判定基准与可声明符合度 10.2 相对标准的缺失(缺口 G1–G6) 10.3 标准之外的内容(本手册不描述) 10.4 行为偏差、环境限制与使用须知 10.5 与 C90/C99 的差异逐项对照(库函数层) 10.6 前端词法/语法层 10.7 使用限制汇总 10.8 GNU99(GNU C 扩展)支持说明
10.1 支持标准、判定基准与可声明符合度
| 项 | 内容 |
|---|---|
| 支持标准 | ISO/IEC 9899 C90(1990) 与 C99(1999) |
| 编译器语言 | 支持 -std=c90/c99 及其 GNU 变体 gnu99。与 C 库相关的差异见 10.8 |
| 类型宽度 | int/long/float/指针 4 字节;long long/double 8 字节;long double==double;char 默认无符号(CHAR_MIN=0、CHAR_MAX=255) |
| 可声明符合 | 符号齐全、行为基本合规:窄 printf/scanf、string/ctype/stdlib/math/time、宽字符内存路径均符合标准 |
| 不可声明 / 受限 | %n(G1)、fenv 舍入方向(G2)、rename(G3)、宽字符 stdio 集成(G4)、fwide/fgetwc(G5)、<math.h> 错误处理(C90)(G6),详见 10.2 / 10.4 |
| 前端语言层 | 语法规则违反与约束违反均会产生诊断;未发现与所声明标准不一致之处(见 10.6) |
10.2 相对标准的缺失(缺口 G1–G6)
| 编号 | 特性 | 现象 | 标准依据 | 影响与规避 |
|---|---|---|---|---|
| G1 | printf 的 %n | %n 不写入目标变量,静默失效 | C90 §7.9.6.1;C99 §7.19.6.1p8 | 仅 printf 族受影响(scanf 的 %n 正常)。不要使用 printf 的 %n |
| G2 | fenv 舍入方向(非 FPU) | 三个方向宏有定义但不生效,fegetround 恒返 FE_TONEAREST | C99 §7.6p8、§7.9.1 | 不支持切换方向。不要依赖舍入方向控制 |
| G3 | rename() | 头文件有声明,交付库无该符号,链接报 undefined symbol: rename | C90 §7.9.4.2;C99 §7.19.4.2 | 不使用 rename;remove() 不受影响 |
| G4 | 宽字符 stdio 集成 | %ls 按 %s 处理;宽流直灌 wchar_t 原始字节 | C99 §7.19.6.1p7-9、§7.24.3.3p2 | %ls/%lc 与宽流编码受影响;宽字符内部接口不受影响。不要用于对外数据交换 |
| G5 | fwide 返值/改向、fgetwc EOF | 无"未定向"状态;fwide 可改向已定向流;fgetwc 不置 EOF 标志 | C99 §7.24.3.5、§7.24.3.1 | 不要用 fwide(fp,0) 判断定向,也不要用 feof() 判断宽字符输入结束(比较 WEOF) |
| G6 | <math.h> 错误处理(C90 口径) | 定义域/值域错误均不置 errno(math_errhandling == 0) | C90 §7.5.1 要求置 EDOM/ERANGE;C99 §7.12p9、§7.12.1 | C90 下为偏差,C99 下为合规声明。不要依赖 errno/浮点异常 |
10.3 标准之外的内容(本手册不描述)
本库在 ISO C90/C99 之外,还实现了若干其它标准/来源的接口(Annex K 边界检查接口、POSIX/XSI、BSD、GNU、SVID,以及 C11 / C23 的部分接口等)。
**这些内容不属于本手册声明的 C90/C99 范围,本手册不作描述,也不构成对其可用性、行为或长期兼容性的承诺。**功能安全项目中如使用,须单独评估并纳入相应的验证范围。
10.4 行为偏差、环境限制与使用须知
| 项 | 现象 / 结论 | 定性 |
|---|---|---|
数学函数不设 errno(C90 下为偏差,见 G6) | math_errhandling == 0;错误处理调用均不置 errno | C99:允许(须以返回值判错);C90:偏差 G6(10.2)。两种模式都不要依赖 errno |
char 符号性 | 默认无符号(CHAR_MIN=0、CHAR_MAX=255);加 -fsigned-char 则有符号 | §6.2.5p15 实现定义。库与用户代码须采用同一符号性,否则 localeconv() 的 char 成员比较不相等 |
clock() 返回 -1 | 无 OS 时钟环境 | §7.23.2.1(合法) |
setlocale 未知名回落 C | setlocale(LC_ALL,"xx_YY") 返非 NULL 并回落 C locale | §7.11.1.1p4 边缘(低危宽松);"C"/"POSIX"/"" 标准要求项全部正确 |
remove() 失败语义 | 无文件系统环境对不存在文件返回 -1(非零) | §7.19.4.1p3 合规(成功返 0 / 失败返非零) |
窄 printf 的 %S | 按字面字符原样输出 | §7.19.6.1p9 允许(%S 非 C99 转换符,无效转换规格行为未定义) |
long double == double | 8 字节 / 53 位 | §5.2.4.2.2 允许;无精度增益,使用 %Lf 等不获得额外精度 |
| 宽字符流编码(G4) | 见 10.2 G4 | 与其他标准工具不互认,影响跨工具互操作 |
fwide 定向 与 fgetwc 结束判定(G5) | 见 10.2 G5 | 不得以 fwide(fp,0) 判断流是否已定向,也不得以 feof() 判宽字符输入结束(比较 WEOF) |
| F1 严格模式命名空间 | 严格模式下 BSD/POSIX 扩展声明仍可见(strdup/asprintf 等) | 合规(§7.1.3 未禁止额外声明;属 §7.26 保留集)。撞名时改用实现保留前缀的名称 |
10.5 与 C90/C99 的差异逐项对照
| # | 特性 | C90 | C99 | 本库实现 | 判定 |
|---|---|---|---|---|---|
| 1 | 头文件 24 个 | 15 头子集 | 要求 24 头 | 24 头全部提供,标准符号齐备(唯一缺失 rename,见 G3) | 无差异 |
| 2 | printf/scanf 的 ll 长度修饰符 | 不适用 | 要求 §7.19.6.1p7 | 满足(%lld/%llu/%llx 等均可用) | 无差异 |
| 3 | printf/scanf 的 L 长度修饰符 | 不适用 | 要求 §7.19.6.1p7 | 满足(%Lf/%Lg/%Le/%La 均可用) | 无差异 |
| 4 | %n 转换(printf 族) | 要求 §7.9.6.1 | 要求 §7.19.6.1p8 | 不满足 —— snprintf("abc%n",&n) 静默不写 | G1(10.2) |
| 5 | %n 转换(scanf 族) | 要求 | 要求 | 满足 | 无差异 |
| 6 | rename() | 要求 §7.9.4.2 | 要求 §7.19.4.2 | 不满足 —— 默认链接链 undefined symbol: rename | G3(10.2) |
| 7 | remove() | 要求 §7.9.4.1 | 要求 §7.19.4.1p3 | 满足(成功返 0 / 失败返非零) | 无差异 |
| 8 | fenv 舍入方向 | 不适用 | 条件要求 §7.6.3.2 | 部分满足 —— 四方向宏有定义,但 fesetround 不生效、fegetround 恒返 FE_TONEAREST | G2(10.2) |
| 9 | 宽字符 stdio 集成(窄 %ls/%lc、宽流编码) | 不适用 | 要求 §7.19.6.1p7-9、§7.24.3.3p2 | 不满足 —— %ls 静默按 %s;宽流向流直灌 wchar_t 4 原始字节 | G4(10.2) |
| 10 | fwide 返值/改向、fgetwc EOF 标志 | 不适用 | 要求 §7.24.3.5p3/p2、§7.24.3.1p3 | 不满足 —— 无"未定向"状态(fwide(f,0) 恒返 ±1);已定向流可被 fwide 改向;fgetwc 到流尾不置 feof() | G5(10.2) |
| 11 | math 函数 errno 语义 | 要求 §7.5.1(定义域 EDOM/值域 ERANGE,无豁免) | 允许 §7.12p2、§7.12.1p5 | math_errhandling == 0,不置 errno | G6(C90 偏差)/合规声明(C99) |
| 12 | tgmath.h 泛型宏 | 不适用 | 要求 §7.22 | 要求宏齐备(55 个实函数 + 5 个复数宏) | 无差异 |
| 13 | inttypes.h PRI/SCN 宏族 | 不适用 | 要求 §7.8.1 | 84 个 PRI* + 70 个 SCN* 全部提供 | 无差异 |
| 14 | wchar.h 头内宏 | 不适用 | 要求 §7.24.1p3 | NULL/WCHAR_MIN/WCHAR_MAX/WEOF 齐备 | 无差异 |
| 15 | setjmp 宏/函数形态 | 允许 §7.6p3 | 允许 §7.13p3 | 函数形态 + __returns_twice 属性 | 无差异 |
| 16 | 可选:FP_FAST_FMA 系 | 不适用 | 可选 §7.12.2 | 未定义(仅性能提示,不影响符合性) | 合规 |
| 17 | 可选:_Imaginary / imaginary | 不适用 | 条件可选 §7.3.1p3 | 不支持(clang 14 无 _Imaginary) | 合规 |
| 18 | 严格模式命名空间 | 要求 §7.1.3 | 要求 §7.1.3 | strdup/asprintf 等扩展声明在严格模式仍可见 | F1(非违反,10.4) |
| 19 | char 符号性;long double 宽度 | 实现定义 | 实现定义 | char 无符号;long double==double | 合规(10.4) |
| 20 | 窄 printf 的 %S | 不适用 | 允许 §7.19.6.1p9 | 按字面字符原样输出 | 合规(10.4) |
| 21 | setlocale 未知名 | 要求返 NULL | 要求返 NULL | 返非 NULL 并回落 C locale | 合规(低危,10.4) |
10.6 前端词法/语法层
前端对标准规定的“必须诊断”项(语法规则违反、约束违反)均会产生诊断,未发现与所声明标准不一致之处。
使用时注意以下几点:
- 关键字随语言变化:
inline、restrict、_Bool、_Complex、_Imaginary在 C99 才成为关键字。同样的代码在-std=c90下可作为普通标识符,在-std=c99下则不能。 -std=c90/-std=c99不等于严格模式://注释、long long、十六进制浮点、VLA、指定初始化器、复合字面量、__VA_ARGS__、GNUcase范围等在默认情况下会被接受;需要严格语言时须显式加-pedantic-errors。- 部分构造在 C99 下只给警告:隐式 int、隐式函数声明、重复类型限定符、存储类说明符重复、不兼容指针赋值等。标准只要求“产生诊断”,警告已满足;若需视为错误,加
-Werror或-pedantic-errors。 - 本目标不可用:
_Imaginary(C99 条件可选)与__int128不支持。
10.7 使用限制汇总
风险等级:高 —— 静默失效,程序不报错但结果或保护不符预期(最危险)|中 —— 结果偏差,可观测但需留意|低 —— 能力边界,如实不提供,调用即失败。
10.7.1 库功能限制
| # | 限制 | 现象 | 影响 | 规避措施 | 等级 |
|---|---|---|---|---|---|
| L1 | 流 / 文件 I/O 不可用(无文件系统) | 任何文件操作都在运行时失败 | 全部流/文件 I/O、字符 I/O 及 printf/scanf 的流版本 | 改用内存版(sprintf/snprintf/sscanf) | 低 |
| L2 | 数学错误报告缺失 | math_errhandling == 0;log(0.0) 不置 ERANGE | 不设 errno、不触发浮点异常;以二者判错会静默失效 | 禁止以 errno/FP 异常判错,改用返回值 | 高 |
| L3 | <fenv.h> 舍入方向不可用 | 无硬件 FPU;fesetround 只认 FE_TONEAREST | 固定就近偶数舍入,fetestexcept 基本恒为 0。不要依赖舍入方向或浮点异常 | 定向舍入须自行实现 | 低/中 |
| L4 | <complex.h> 超越函数精度不保证 | 复数超越函数(cacos/cpow 等)精度与 branch-cut 行为不符预期 | 精度不可预期;未定义 __STDC_IEC_559__,Annex G 不在承诺范围 | 精度敏感场合不要使用 | 中 |
| L5 | printf 族 %n 转换 | 与 10.2 G1 同源:%n 不写入目标变量 | 用 %n 累加偏移的代码会算错位置,存在缓冲区越界风险 | 禁止使用 %n | 高 |
| L6 | snprintf/sprintf 十进制舍入边界 | 末位舍入、序列点与返回值与预期不符 | 浮点转十进制在末位边界可能超出正确舍入上界 | 不要依赖最后一位;比较时留 1 ULP 余量 | 中 |
| L7 | <wchar.h> wscanf 返回值 | wscanf 返回的项数与预期不符 | 按返回值判断输入是否成功的代码不可靠 | 不依赖 wscanf 返回值 | 中 |
| L8 | rename() 缺失 | 与 10.2 G3 同源;链接期 undefined symbol: rename | 默认链接链无该符号,编译通过但链接失败 | 不使用 rename(无文件系统场景亦无从使用) | 低 |
| L9 | 宽字符流编码(G4) | %ls 静默按 %s;宽流直灌 wchar_t 原始字节 | 与其他标准工具不互认 | 不要用于对外数据交换 | 中 |
| L10 | fwide 定向 / fgetwc EOF(G5) | 无"未定向"状态;fwide 可改向;fgetwc 不置 feof() | — | 不要用 fwide(fp,0) 判断定向;判结束比较 WEOF | 中 |
关于 L2 的定性:本项即 10.2 新增的 G6。在 C99 口径下可援引 §7.12p2 / §7.12.1p5 归为「合规声明(须遵守)」;但 C90 §7.5.1 直接要求置
errno(定义域EDOM/ 值域ERANGE)且无豁免条款,故 C90 口径下为偏差 —— C90 口径下错误处理调用均不置errno。使用侧行动:C90 与 C99 模式下一律不得依赖errno/ FP 异常。
内存聚合:凡在内存中完成的格式化与解析(
sprintfsnprintfvsnprintfvsprintfsscanfvsscanf、swprintfswscanfvswscanf)均可用;凡依赖文件/流的(L1)均不可用。这一条是嵌入式使用中最需要先确认的边界。
10.7.2 前端(编译器)诊断限制
有 10 类构造在 -pedantic-errors 下仍不会产生诊断,例如混合指向(不)完整类型的指针、带 typedef 存储类的局部函数声明、位域类型违反约束、空标识符列表的原型冲突、非法结构体初始化、文件作用域的暂定定义等。
不得依赖编译器在编译期拦住这些构造,须由编码规范或静态检查兜底。此项与库实现无关。
10.8 GNU99 语言(GNU C 扩展)支持说明
本节说明
-std=gnu99相对-std=c99的差异,以及这些差异对 C 库头文件声明可见性的影响。 定位:GNU 扩展不属于 ISO C90/C99,本节为语言使用须知。
10.8.1 支持范围与默认值
| 项 | 内容 |
|---|---|
| 编译器支持的 C 语言 | -std=c90、c99、c11 及对应 GNU 变体 gnu99 |
-std=gnu99 的标准等级 | 仍为 C99(__STDC_VERSION__ == 199901L),在此基础上启用 GNU 扩展 |
| 代码生成 / ABI | 与 -std=c99 一致 |
10.8.2 编译器层差异
| 项 | -std=c99 | -std=gnu99 | 说明 |
|---|---|---|---|
__STRICT_ANSI__ | 定义 | 不定义 | 两种语言下唯一的预定义宏差异 |
typeof | 普通标识符 | 关键字 | c99 下误用会先报隐式函数声明、再报语法错误,编译失败 |
asm(文件作用域) | 普通标识符 | 关键字 | c99 下报 error: expected parameter declarator;__asm__ 两种模式均可用 |
非 int 返回的 main | error | warning | 如 float main(void):c99 拒绝,gnu99 仅告警并生成产物 |
空 __VA_ARGS__ 的 , ##(宏仅有 ... 形参) | 逗号保留 | 逗号删除 | c99 展开为 g(0,)(语法错),gnu99 展开为 g(0) |
<limits.h> 的 LONG_LONG_MIN/MAX、ULONG_LONG_MAX | 不定义 | 定义 | 编译器自带 limits.h 的非 __STRICT_ANSI__ 分支;标准宏 LLONG_MAX 两种模式都有 |
GNU 专属内建库函数 finite/finitef/finitel | 不作为内建 | 作为内建 | 非 GNU 模式下退化为普通隐式函数声明(仍可链接,因 libc.a 提供同名符号) |
-fgnu-keywords 默认值 | false | true | 两个开关相互独立,可显式覆盖(见下) |
-pedantic-errors 下对 GNU 扩展 | 拒绝 | 同样拒绝 | gnu99 不豁免 ISO 一致性检查(语句表达式 / case 范围 / 零长数组 / typeof 均报错) |
inline / restrict | 关键字 | 关键字 | 无差异(C99 关键字) |
可移植写法:
__typeof__、__asm__、__inline__、__restrict在-std=c99与-std=gnu99下均可用;需要跨标准复用时优先采用双下划线形式。
10.8.3 对 C 库头文件可见性的影响
交付的 picolibc/newlib 头文件通过 sys/features.h 判断 __STRICT_ANSI__:未定义时自动定义 _DEFAULT_SOURCE=1 与 _POSIX_C_SOURCE=202405L。因此同一份源码在不同语言下可见的库声明不同:
| 声明(示例) | -std=c99 | -std=gnu99 | 说明 |
|---|---|---|---|
alloca | 隐式声明 | 可见 | gnu99 下 stdlib.h 自动包含 <alloca.h> |
random / srandom | 隐式声明 | 可见 | |
usleep | 隐式声明 | 可见 | |
index / rindex | 隐式声明 | 可见 | |
bzero | 隐式声明 | 可见 | |
strlcpy | 隐式声明 | 可见 | |
memmem | 隐式声明 | 隐式声明 | 需显式 -D_GNU_SOURCE(加该宏后可见) |
popen | 隐式声明 | 隐式声明 | 本交付配置未声明(加 _GNU_SOURCE 后仍不可见;能力边界,与语言无关) |
strcasecmp / strncasecmp | 可见 | 可见 | 需包含 <strings.h>;语言无差异 |
在 -std=c99 下恢复声明的正确方法:
-D_DEFAULT_SOURCE=1可恢复random/srandom/usleep/index/rindex/bzero/strlcpy等;随后仅alloca、memmem、popen仍为隐式声明。alloca必须显式#include <alloca.h>——stdlib.h中的包含由#ifndef __STRICT_ANSI__控制,特征宏无法打开该分支。- 仅定义
_POSIX_C_SOURCE=200809L不足以恢复usleep/random。 memmem等 GNU 专属接口需-D_GNU_SOURCE。
注意:风险提示(隐式声明):本工具链(clang 14.0.6)中隐式函数声明仅产生 warning、不中止编译。
alloca会被推断为返回int,与void*类型不匹配(产生incompatible integer to pointer conversion诊断);更一般地,任何隐式声明都会按int返回值处理,可能造成未定义行为。使用方应检查编译日志,或启用-Werror=implicit-function-declaration;不能把"编译成功"当作头文件已正确包含的依据。
10.8.4 使用须知
- 显式指定语言:产品构建应固定写入
-std=c99或-std=gnu99,不要依赖默认值。 - ISO 可追溯场景:使用
-std=c99;确需 POSIX/GNU 接口时,按 10.8.3 显式定义特性宏并包含对应头文件。 - 使用 GNU 扩展的场景:GNU 扩展(
typeof/asm/语句表达式/case 范围/零长数组等)不属于 ISO C,须纳入编码规范与静态检查;功能安全项目中应单独评估并纳入验证范围。 -pedantic-errors仍然有效:-std=gnu99不表示放弃 ISO 一致性诊断;启用该选项时 GNU 扩展会被拒绝。