跳到主要内容

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.8GNU99(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)​

编号特性现象标准依据影响与规避
G1printf 的 %n%n 不写入目标变量,静默失效C90 §7.9.6.1;C99 §7.19.6.1p8仅 printf 族受影响(scanf 的 %n 正常)。不要使用 printf 的 %n
G2fenv 舍入方向(非 FPU)三个方向宏有定义但不生效,fegetround 恒返 FE_TONEARESTC99 §7.6p8、§7.9.1不支持切换方向。不要依赖舍入方向控制
G3rename()头文件有声明,交付库无该符号,链接报 undefined symbol: renameC90 §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 与宽流编码受影响;宽字符内部接口不受影响。不要用于对外数据交换
G5fwide 返值/改向、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.1C90 下为偏差,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;错误处理调用均不置 errnoC99:允许(须以返回值判错);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 未知名回落 Csetlocale(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 == double8 字节 / 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 的差异逐项对照​

#特性C90C99本库实现判定
1头文件 24 个15 头子集要求 24 头24 头全部提供,标准符号齐备(唯一缺失 rename,见 G3)无差异
2printf/scanf 的 ll 长度修饰符不适用要求 §7.19.6.1p7满足(%lld/%llu/%llx 等均可用)无差异
3printf/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 族)要求要求满足无差异
6rename()要求 §7.9.4.2要求 §7.19.4.2不满足 —— 默认链接链 undefined symbol: renameG3(10.2)
7remove()要求 §7.9.4.1要求 §7.19.4.1p3满足(成功返 0 / 失败返非零)无差异
8fenv 舍入方向不适用条件要求 §7.6.3.2部分满足 —— 四方向宏有定义,但 fesetround 不生效、fegetround 恒返 FE_TONEARESTG2(10.2)
9宽字符 stdio 集成(窄 %ls/%lc、宽流编码)不适用要求 §7.19.6.1p7-9、§7.24.3.3p2不满足 —— %ls 静默按 %s;宽流向流直灌 wchar_t 4 原始字节G4(10.2)
10fwide 返值/改向、fgetwc EOF 标志不适用要求 §7.24.3.5p3/p2、§7.24.3.1p3不满足 —— 无"未定向"状态(fwide(f,0) 恒返 ±1);已定向流可被 fwide 改向;fgetwc 到流尾不置 feof()G5(10.2)
11math 函数 errno 语义要求 §7.5.1(定义域 EDOM/值域 ERANGE,无豁免)允许 §7.12p2、§7.12.1p5math_errhandling == 0,不置 errnoG6(C90 偏差)/合规声明(C99)
12tgmath.h 泛型宏不适用要求 §7.22要求宏齐备(55 个实函数 + 5 个复数宏)无差异
13inttypes.h PRI/SCN 宏族不适用要求 §7.8.184 个 PRI* + 70 个 SCN* 全部提供无差异
14wchar.h 头内宏不适用要求 §7.24.1p3NULL/WCHAR_MIN/WCHAR_MAX/WEOF 齐备无差异
15setjmp 宏/函数形态允许 §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.3strdup/asprintf 等扩展声明在严格模式仍可见F1(非违反,10.4)
19char 符号性;long double 宽度实现定义实现定义char 无符号;long double==double合规(10.4)
20窄 printf 的 %S不适用允许 §7.19.6.1p9按字面字符原样输出合规(10.4)
21setlocale 未知名要求返 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__、GNU case 范围等在默认情况下会被接受;需要严格语言时须显式加 -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 不在承诺范围精度敏感场合不要使用中
L5printf 族 %n 转换与 10.2 G1 同源:%n 不写入目标变量用 %n 累加偏移的代码会算错位置,存在缓冲区越界风险禁止使用 %n高
L6snprintf/sprintf 十进制舍入边界末位舍入、序列点与返回值与预期不符浮点转十进制在末位边界可能超出正确舍入上界不要依赖最后一位;比较时留 1 ULP 余量中
L7<wchar.h> wscanf 返回值wscanf 返回的项数与预期不符按返回值判断输入是否成功的代码不可靠不依赖 wscanf 返回值中
L8rename() 缺失与 10.2 G3 同源;链接期 undefined symbol: rename默认链接链无该符号,编译通过但链接失败不使用 rename(无文件系统场景亦无从使用)低
L9宽字符流编码(G4)%ls 静默按 %s;宽流直灌 wchar_t 原始字节与其他标准工具不互认不要用于对外数据交换中
L10fwide 定向 / 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 异常。

内存聚合:凡在内存中完成的格式化与解析(sprintf snprintf vsnprintf vsprintf sscanf vsscanf、swprintf swscanf vswscanf)均可用;凡依赖文件/流的(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 返回的 mainerrorwarning如 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 默认值falsetrue两个开关相互独立,可显式覆盖(见下)
-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 使用须知​

  1. 显式指定语言:产品构建应固定写入 -std=c99 或 -std=gnu99,不要依赖默认值。
  2. ISO 可追溯场景:使用 -std=c99;确需 POSIX/GNU 接口时,按 10.8.3 显式定义特性宏并包含对应头文件。
  3. 使用 GNU 扩展的场景:GNU 扩展(typeof/asm/语句表达式/case 范围/零长数组等)不属于 ISO C,须纳入编码规范与静态检查;功能安全项目中应单独评估并纳入验证范围。
  4. -pedantic-errors 仍然有效:-std=gnu99 不表示放弃 ISO 一致性诊断;启用该选项时 GNU 扩展会被拒绝。