说明:整个C标准库解剖系列环境为Ubuntu 8.04,编译器为gcc 4.2.4,由于linux系统中只有C标准库的头文件(在/usr/include下),函数库被编译成了程序库,没有源代码,因此对源代码的解剖用的是glibc 2.9,可从GNU的官方站点上下载。
类型相关定义包括limits.h、float.h、stddef.h、stdbool.h、stdarg.h、iso646.h、stdint.h共7个头文件。除了stdint.h外,其余6个文件在gcc编译器的/usr/lib/gcc/i486-linux-gnu/4.2.4/include目录下。stdint.h在/usr/include中,是C99中引入的,提供了扩展整数的基本定义,放到后面再解剖吧。
1、limits.h: 定义了整数类型的范围。/usr/include下也有limits.h,它会自己先定义各个整数类型范围,这样当不用gcc来构建你的程序时就可以使用这些值。如果使用gcc编译器来构建你的程序,则会使用gcc编译器自己的limits.h(前面的定义都会#undef掉)。由于这个limits.h会用到gcc内置的limits.h,因此我们解剖/usr/include下的limits.h。
解释:
(1)/usr/include/limits.h的实现中,char=unsigned char占8位,short占16位,int占32位,long在64位平台上占64位,在32位平台上占32位,C99标准引入的long long占64位。它们都有singed和unsigned两种,默认都是带符号整数(signed)。带符号整数在当前大多数体系结构上一般都用二进制补码表示(当然C标准也支持用其他一些编码表示),即正数用直接编码,符号位为0;负数表示为对应正数各位取反然后加1,符号位为1。带符号整数范围为-2**(n-1)~2**(n-1)-1,其中最小负数-2**(n-1)=100...0没有对应正数,其反数还是自己。无符号整数用直接二进制编码,范围为0~2**n-1。如果使用gcc的limits.h,则每个宏的值依赖于gcc编译器内置的定义,一般跟这里的值一致。
(2)UCHAR_MAX必须等于2**CHAR_BIT-1,且对带符号整数一般有MIN=-MAX-1。
(3)feature.h文件中定义了一些表示编译选项的宏,如ISOC99选项、POSIX选项、XOPEN选项等。bits/wordsize.h定义了表示字的位数的__WORDSIZE宏,64位平台上值为64,32位平台上值为32。它们都在/usr/include下。
(4)如果要新遵循C99标准,则有些gcc版本的<limits.h>可能没有定义LLONG_MIN、LLONG_MAX和ULLONG_MAX,则这里需要进行定义。如果使用POSIX标准,则还要添加一些POSIX中的东西。
2、float.h: 定义了浮点数类型的特征。
解释:
(1)浮点数的形式为x=s*(b**e)*[f1*b**(-1)+f2*b**(-2)+...+fp*b**(-p)], emin<=e<=emax(**表示求幂)。s是符号位,b是进制基数,e是指数值,p是b进制的有效位数,0<=fk<b。
(2)IEEE的浮点数表示法:单精度float型有1位符号位S,8位指数E,23位尾数M。转换成数值V=(-1)**S*1.M*2**(E-127)。例如16.5=00010000.1=1.00001*2**4(成为规格化数),则符号位为0,指数位为4+127=131=10000011(因为指数可以为负,8位有符号数的范围为-128~127,为了统一用无符号数表示,要加上127),尾数为00001000000000000000000,拼接起来即得到16.5的内存表示01000001100001000000000000000000。
(3)常用的宏有FLT_DIG/DBL_DIG/LDBL_DIG、FLT_MIN/DBL_MIN/LDBL_MIN、FLT_MAX/DBL_MAX/LDBL_MAX。
3、stddef.h: 定义了ptrdiff_t、size_t、wchar_t、wint_t类型和offsetof。有一大堆兼容不同平台的条件编译宏,这对我们没什么用,略去。
ptrdiff_t是两个指针相减所得的带符号整型,一般用long类型表示。size_t是sizeof运算得到的无符号整型,一般用unsigned int或unsigned long表示。宽字符类型wchar_t也在stddef.h中定义,这里为int类型。wint_t用于无符号的宽字符类型中,这里为unsigned int类型。offsetof宏用于计算结构成员的地址偏移字节数。
4、stdbool.h: 是C99中增加的,定义了布尔类型bool,及其两个常量false=0、true=1。__bool_true_false_are_defined=1是标识布尔类型定义是否完成的信号。这些定义与C++中的一致,因此标准C++并不需要另外再支持stdbool.h,但GCC提供了这个扩展,使得在C++中可以支持<stdbool.h>。
5、stdarg.h: 访问可变参数表的类型和函数(用宏实现)。当你需要编写有可变参数表的函数时,比如myfunc(int *a,...),你就可以用stdarg.h中的各个函数来遍历“...”中的各个实参,以完成该函数的功能。略去没有用的一大堆用于兼容不同平台的条件编译宏,如下:
解释:
(1)va_list类型:用这种类型来定义遍历可变参数列表的状态变量ap。
(2)va_start(ap,lt):让ap的内部指针指向第一个可变参数。需要用lt来指定可变参数表前面的最后一个固定参数。遍历开始必须先调用这个函数。
(3)var_arg(ap,type):获取当前ap内部指针指向的参数值,然后把指针移动下一个参数,下一个参数的类型要用type指定。
(4)va_end(ap):完成对可变参数表的遍历,会对ap和参数表作必要的整理工作。遍历结束时必须要调用这个函数。
(5)va_copy(dest,src):c99中引入,将src复制到dest中,dest和src均为va_list型状态变量。这样就生成指向当前参数的第二个状态变量,然后可以独立的使用src和dest来遍历可变参数表。dest中也要像src中一样调用va_end。
6、iso646.h: 为逻辑运算符定义一些方便使用的宏,是C89增补1中增加的。
之所以要为&&、|、!、^等这些运算符定义一个宏,是因为在ISO 646的字符集中要使用这些特殊的符号可能不方便,而用等价的宏名and、bitor、not、xor就比较方便了,在C++中这些宏名是关键字。C89增补1还提供了一些能在ISO 646中方便使用的字符来拼写{、}之类的符号。如<%、%>、<:、:>、%:、%:%分别等价于字符{、}、[、]、#、##。