库简介(Library Introduction)
章节大纲
-
术语定义(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
,并设置errno
为ERANGE
,但这两个宏分别定义在<math.h>
和<errno.h>
中,因此使用时也要包含这两个头文件。为实现自包含性,多个头文件可能定义相同的标识符(例如
NULL
和size_t
)。标准库中的所有函数都使用函数原型进行声明。
标准头文件可以任意顺序、多次包含,不会产生不良影响。唯一的例外是
<assert.h>
,如果多次包含,且NDEBUG
宏的定义状态发生变化,行为可能会不同。为了完全符合标准,禁止在外部声明或定义中包含标准头文件,也就是说不应在函数内部包含标准头文件,因为函数属于外部定义。
许多标准库函数的原型使用了 C89 引入或采用的关键字和派生类型,如
const
、fpos_t
、size_t
和void *
。在对老函数应用这些特性时,它们与旧式调用仍兼容。标准 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>
取代
-