C编程
C标准库是一个标准化的头文件和库例程集合,用于实现常见操作,如输入/输出和字符字符串处理。与其他语言(如COBOL、Fortran和PL/I)不同,C语言没有内建的关键字来执行这些任务,因此几乎所有C程序都依赖标准库来操作。
历史
C编程语言最初并没有提供任何基础函数,如I/O操作。随着时间的推移,C的用户社区共享了想法和实现来提供这些功能。这些想法逐渐变得普遍,并最终在1989年被纳入标准化的C语言定义中。这些实现现在被称为C标准库。
Unix和C语言都是在AT&T贝尔实验室于1960年代末和1970年代初期创建的。在1970年代,C编程语言变得越来越流行,许多大学和组织开始为他们自己的项目创建C语言的变体。到1980年代初期,不同C实现之间的兼容性问题变得显著。1983年,美国国家标准协会(ANSI)成立了一个委员会,制定C语言的标准规范,称为“ANSI C”。这项工作最终在1989年形成了所谓的C89标准。由此产生的标准的一部分是一个称为ANSI C标准库的软件库集。
C标准的后续修订增加了几个新的必要头文件。对于这些新扩展的支持在不同的实现中有所不同。
头文件 <iso646.h>
、<wchar.h>
和 <wctype.h>
是在1995年通过标准附录1(简称NA1)添加的。
头文件 <complex.h>
、<fenv.h>
、<inttypes.h>
、<stdbool.h>
、<stdint.h>
和 <tgmath.h>
是在C99中增加的,该修订版本于1999年发布。
注意:
C++编程语言包含了ANSI C89标准库中的函数,但对这些函数做了一些修改,例如将所有标识符放入std
命名空间,并将头文件名从 <xxx.h>
改为 <cxxx>
(尽管仍然可以使用C风格的名字,尽管它们已被弃用)。
设计
每个函数的声明都保存在一个头文件中,而函数的实际实现则被分离到库文件中。头文件的命名和范围已经变得标准化,但库的组织结构仍然保持多样性。标准库通常与编译器一起提供。由于C编译器通常提供一些在ANSI C中未指定的额外函数,因此某些特定编译器的标准库通常与其他编译器的标准库不兼容。
C标准库的许多部分被证明是设计良好的。回顾历史,有些部分被认为是错误的。例如,字符串输入函数gets()
(以及使用scanf()
读取字符串输入)是许多缓冲区溢出的源头,绝大多数编程指南建议避免使用这些函数。另一个怪异之处是strtok()
,一个被设计为基本词法分析器的函数,但它非常“脆弱”,且难以使用。
ANSI标准
ANSI C标准库包含24个C头文件,可以通过单个指令包含到程序员的项目中。每个头文件包含一个或多个函数声明、数据类型定义和宏。以下是这些头文件的内容。
与某些其他语言(例如Java)相比,标准库非常小。它提供了基本的数学函数、字符串处理、类型转换以及文件和控制台的I/O功能。它不包含像C++标准模板库那样的标准“容器类型”,更不用说Java标准库中提供的完整图形用户界面(GUI)工具包、网络工具和其他大量功能。标准库的小型化的主要优点是,提供一个可用的ANSI C环境比其他语言要容易得多,因此将C移植到新平台的难度相对较小。
许多其他库已经被开发出来,用来提供与其他语言标准库中相同功能的实现。例如,GNOME桌面环境项目开发了GTK+图形工具包和GLib,一个容器数据结构库,还有许多其他知名的示例。由于可用库的多样性,某些优越的工具包通过历史证明了它们的价值。其显著的缺点是,这些库往往无法很好地协同工作,程序员往往熟悉不同的库集合,而在某个平台上可能提供不同的库集合。
ANSI C库头文件
- <assert.h>:包含assert宏,用于帮助在调试版本的程序中检测逻辑错误和其他类型的bug。
- <complex.h>:用于操作复数的函数集合(C99新增)。
- <ctype.h>:包含用于按类型分类字符或在不同字符集(通常为ASCII或其扩展,尽管也有基于EBCDIC的实现)中进行大小写转换的函数。
- <errno.h>:用于测试库函数报告的错误代码。
- <fenv.h>:用于控制浮点环境(C99新增)。
- <float.h>:包含定义常量,指定浮点库的实现特性,如最小差异(_EPSILON)、最大精度(_DIG)和可以表示的数字范围(_MIN,_MAX)。
- <inttypes.h>:用于精确的整数类型转换(C99新增)。
- <iso646.h>:用于编程ISO 646变体字符集(NA1新增)。
- <limits.h>:包含定义常量,指定整数类型的实现特性,如可以表示的数字范围(_MIN,_MAX)。
- <locale.h>:用于setlocale()及相关常量。用于选择适当的区域设置。
- <math.h>:用于计算常见数学函数。
- <setjmp.h>:用于非局部跳转的setjmp和longjmp。
- <signal.h>:用于控制各种异常情况。
- <stdarg.h>:用于访问传递给函数的可变数量的参数。
- <stdbool.h>:用于布尔数据类型(C99新增)。
- <stdint.h>:用于定义各种整数类型(C99新增)。
- <stddef.h>:用于定义几个有用的类型和宏。
- <stdio.h>:提供C语言的核心输入输出功能。此文件包括著名的
printf
函数。 - <stdlib.h>:用于执行各种操作,包括转换、伪随机数、内存分配、进程控制、环境、信号、搜索和排序。
- <string.h>:用于操作几种类型的字符串。
- <tgmath.h>:用于类型泛化数学函数(C99新增)。
- <time.h>:用于转换不同的时间和日期格式。
- <wchar.h>:用于操作宽字符流和多种类型的宽字符字符串,是支持多种语言的关键(NA1新增)。
- <wctype.h>:用于分类宽字符(NA1新增)。
常见支持库
尽管没有标准化,C程序可能依赖于一个运行时库,其中包含编译器在运行时使用的代码。例如,在调用main()
之前,操作系统用来初始化进程的代码通常实现于C运行时库。C运行时库代码还可能帮助实现其他语言特性,例如处理未捕获的异常或实现浮点代码。
C标准库只文档化了本文提到的特定例程及其行为。由于编译器的实现可能依赖于这些附加的实现级函数,因此编译器特定的例程通常与C标准库捆绑在一起,因为它们在使用该工具集编译的任何程序中都可能需要。
尽管通常因其捆绑而与C标准库混淆,C运行时库并不是语言的标准化部分,而是供应商特定的。
编译器内建函数
一些编译器(例如GCC)提供了许多C标准库函数的内建版本;也就是说,函数的实现被写入编译后的目标文件中,程序调用的是内建版本,而不是C库共享对象文件中的函数。这减少了函数调用的开销,尤其是当函数调用被内联变体替代时,并允许其他形式的优化(因为编译器知道内建变体的控制流特征),但在调试时可能会造成困惑(例如,内建版本无法替换为有仪器的变体)。
POSIX标准库
POSIX(以及单一Unix规范)指定了一些例程,这些例程应当在C标准库之外提供;这些通常与C标准库函数一起实现,并且有不同程度的紧密度。例如,glibc在libc.so中实现了如fork
等函数,但在NPTL合并到glibc之前,它曾是一个独立的库,并有自己的链接器标志。通常,这些POSIX指定的函数被视为库的一部分;C库本身可能被标识为ANSI或
ISO C库。
POSIX认可的库包括:
- c:提供POSIX.1-2008系统接口卷中引用的所有接口(可能不包括一些接口,如
<aio.h>
等中的接口)。 - l:提供lex程序输出需要的接口,lex用fl而不是l。
- pthread:提供
<pthread.h>
中引用的所有接口以及在<signal.h>
中引用的pthread_kill()
和pthread_sigmask()
。 - m:提供
<math.h>
、<complex.h>
和<fenv.h>
中引用的所有接口。 - rt:提供
<aio.h>
、<mqueue.h>
、<sched.h>
等中引用的所有接口。 - trace:提供
<trace.h>
中引用的所有接口。 - xnet:提供
<arpa/inet.h>
、<netdb.h>
等中引用的所有接口。 - y:提供yacc程序输出需要的接口。