Section outline

  • 术语定义(Definition of Terms)

    在 K&R 中,字符(character)的类型是 char,字符串是 char 类型的数组。C89 引入了多字节字符串移位序列的概念,以及宽字符(wchar_t)和宽字符串(wchar_t[]。C89 标准库中也包含了处理这些类型的函数,后续版本的标准又添加了更多的头文件和函数。

    在 C89 之前,C 语言库以所谓的“美国英语”模式运行,例如 printf 所用的小数点为英文句点 .。C89 引入了区域设置(locale)的概念,规定传统 C 语言环境被称为 "C" 区域,并引入了 <locale.h> 头文件。某些标准 C 库函数的行为会受到当前区域设置的影响,也就是说它们具有区域依赖性


    标准头文件(The Standard Headers)

    必需内容(Required Contents)

    C89 定义了以下头文件:

    <assert.h>, <ctype.h>, <errno.h>, <float.h>, <limits.h>, <locale.h>, <math.h>,
    <setjmp.h>, <signal.h>, <stdarg.h>, <stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>
    

    后续标准新增:

    • C95<iso646.h>, <wchar.h>, <wctype.h>

    • C99<complex.h>, <fenv.h>, <inttypes.h>, <stdbool.h>, <stdint.h>, <tgmath.h>

    • C11<stdalign.h>, <stdatomic.h>, <stdnoreturn.h>, <threads.h>, <uchar.h>

    • C17:未新增任何头文件

    这些头文件名称必须全为小写,并且标准实现必须识别这些拼写。虽然某些文件系统支持大小写混合,但标准头文件的拼写不能被更改

    每个标准头文件都是自包含的,即它包含调用其中声明函数所需的全部声明与定义。但不一定包含其函数可能返回的所有宏定义。例如,<stdlib.h> 中的 strtod 函数可能返回 HUGE_VAL,并设置 errnoERANGE,但这两个宏分别定义在 <math.h><errno.h> 中,因此使用时也要包含这两个头文件。

    为实现自包含性,多个头文件可能定义相同的标识符(例如 NULLsize_t)。

    标准库中的所有函数都使用函数原型进行声明。

    标准头文件可以任意顺序、多次包含,不会产生不良影响。唯一的例外是 <assert.h>,如果多次包含,且 NDEBUG 宏的定义状态发生变化,行为可能会不同。

    为了完全符合标准,禁止在外部声明或定义中包含标准头文件,也就是说不应在函数内部包含标准头文件,因为函数属于外部定义。

    许多标准库函数的原型使用了 C89 引入或采用的关键字和派生类型,如 constfpos_tsize_tvoid *。在对老函数应用这些特性时,它们与旧式调用仍兼容

    标准 C 要求宿主环境(hosted implementation)必须支持该版本所定义的所有标准头文件。而自由环境(freestanding implementation)只需支持以下内容:

    • C89<float.h>, <limits.h>, <stdarg.h>, <stddef.h>

    • C95:额外要求 <iso646.h>

    • C99:添加 <stdbool.h>, <stdint.h>

    • C11:添加 <stdalign.h>, <stdnoreturn.h>

    • C17:无新增要求


    可选内容(Optional Contents)

    C11 添加了一个附录“边界检查接口(Bounds-checking interfaces)”,提供一系列可选扩展,用于增强程序的安全性,这些扩展包括新的函数、宏和类型,声明或定义在已有的标准头文件中。

    如果实现定义了宏 __STDC_LIB_EXT1__,则必须提供该附录中的所有扩展。这些扩展适用于以下头文件:

    <errno.h>, <stddef.h>, <stdint.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>, <wchar.h>
    

    如果程序在包含这些头文件前定义了宏 __STDC_WANT_LIB_EXT1__0,则可以禁用这些扩展;若定义为 1,则启用扩展。


    保留标识符(Reserved Identifiers)

    所有在标准头文件中声明的外部标识符都是保留的,无论你是否实际包含该头文件
    例如:即使你从未包含 <time.h>,也不能安全地自定义一个名为 clock 的外部函数。

    但宏名和 typedef不属于外部标识符,因此不受此限制。

    所有以下划线开头的外部标识符是保留的,其他库标识符应以双下划线或下划线+大写字母开头。


    标准库函数的使用(Use of Library Functions)

    并非所有标准库函数都会检查其输入参数的有效性,如果传入无效参数,行为将是未定义的

    实现允许将任何标准库函数实现为宏,前提是这些宏是安全可展开的。也就是说,如果参数包含副作用(如函数调用、++i),展开后也不应造成副作用重复。

    如果你包含了标准头文件,不应自行显式声明其中的函数,否则可能与头文件中的宏定义冲突,从而引发错误。

    在使用库函数的地址时需注意,函数可能是宏形式。可以先使用 #undef,或使用括号包围的形式 (name) 来获取其地址。即使不使用 #undef也可以在同一作用域中同时调用宏版本与函数版本

    强烈建议在使用库函数时包含相应头文件。如果不包含,应显式用函数原型声明该函数,尤其是如 printf 这类变参函数。
    原因是:使用原型时,编译器会明确知道实参数量与类型,甚至可以将前几个参数通过寄存器传递;而没有原型时,调用机制不同,可能导致与库中定义不兼容而链接失败。


    非标准头文件(Non-Standard Headers)

    早期 UNIX C 库中包含通用与系统相关的函数,大多数通用部分被 C89 采纳,系统相关部分由 IEEE POSIX 接管。

    部分函数和宏被两个标准共同使用,或都未采用,它们被合理划分到了各自标准中。但有极少数函数或宏在两个标准中声明或定义方式不同,例如 <limits.h>

    不过,两者的共同目标是:C 程序应当能同时符合 ISO C 与 POSIX 标准

    许多常见但不属于标准 C 库的头文件包括:

    bios.h, conio.h, curses.h, direct.h, dos.h, fcntl.h, io.h, process.h,
    search.h, share.h, stat.h, sys/locking.h, sys/stat.h, sys/timeb.h, sys/types.h, values.h
    

    有些头文件虽未被 C89 采纳,但其功能已被其他标准头文件覆盖或整合,例如:

    • malloc.h → 整合进 <stdlib.h>

    • memory.h → 功能转入 <string.h>

    • varargs.h → 被 <stdarg.h> 取代