說明:整個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。
-
- #ifndef _LIBC_LIMITS_H_
- #define _LIBC_LIMITS_H_ 1
- #include <features.h> /* 選項的宏,如ISOC99選項、POSIX選項、XOPEN選項等 */
- #define MB_LEN_MAX 16 /* 支持區域設置的多字節字符寬度為16位 */
-
- #if !defined __GNUC__ || __GNUC__ < 2
- # ifndef _LIMITS_H
- # define _LIMITS_H 1
- #include <bits/wordsize.h> /* 定義了表示字的位數的__WORDSIZE宏,64位平臺上值為64,32位平臺上值為32 */
- # define CHAR_BIT 8 /* char類型的寬度為8位 */
- # define SCHAR_MIN (-128) /* signed char的最小值為-2^7,補碼表示為10000000,沒有對應的正數,其反數還是自己 */
- # define SCHAR_MAX 127 /* signed char的最大值為2^7-1=01111111 */
- # define UCHAR_MAX 255 /* unsigend char的最大值為2^8-1=11111111(最小值為0) */
- # ifdef __CHAR_UNSIGNED__ /* 根據預定義宏來確定是讓char=unsigned char還是char=signed char */
- # define CHAR_MIN 0
- # define CHAR_MAX UCHAR_MAX
- # else
- # define CHAR_MIN SCHAR_MIN /* gcc中使用了這個,即char=signed char */
- # define CHAR_MAX SCHAR_MAX
- # endif
- # define SHRT_MIN (-32768) /* signed short int的最小值為-2^15 */
- # define SHRT_MAX 32767 /* signed short int的最小值為2^15-1 */
- # define USHRT_MAX 65535 /* unsigned short int的最大值為2^16-1(最小值為0) */
- # define INT_MIN (-INT_MAX - 1) /* int的最小值為-2^31 */
- # define INT_MAX 2147483647 /* int的最大值為2^31-1 */
- # define UINT_MAX 4294967295U /* unsigned int的最大值為2^32-1(最小值為0) */
- # if __WORDSIZE == 64 /* 64位的x86平臺,這個宏在<bits/wordsize.h>中 */
- # define LONG_MAX 9223372036854775807L /* signed long int最大值為2^63-1 */
- # else /* 32位的x86平臺 */
- # define LONG_MAX 2147483647L /* signed long int最大值為2^31-1 */
- # endif
- # define LONG_MIN (-LONG_MAX - 1L) /* signed long int最小值為-2^31 */
- # if __WORDSIZE == 64
- # define ULONG_MAX 18446744073709551615UL /* 64平臺:unsigend long int最大值為2^64-1 */
- # else
- # define ULONG_MAX 4294967295UL /* 32位平臺:unsigned long int最大值為2^32-1 */
- # endif
- # ifdef __USE_ISOC99 /* <feature.h>中宏:long long類型為C99標準引入的 */
- # define LLONG_MAX 9223372036854775807LL /* signed long long int最大值為2^63-1 */
- # define LLONG_MIN (-LLONG_MAX - 1LL) /* signed long long int最小值為-2^63 */
- # define ULLONG_MAX 18446744073709551615ULL /* unsigned long long int最大值為2^64-1 */
- # endif /* ISO C99 */
- # endif /* limits.h */
- #endif /* GCC 2. */
- #endif /* !_LIBC_LIMITS_H_ */
-
- #if defined __GNUC__ && !defined _GCC_LIMITS_H_ /* _GCC_LIMITS_H_是GCC的文件定義 */
- # include_next <limits.h>
- #endif
-
- #if defined __USE_ISOC99 && defined __GNUC__
- # ifndef LLONG_MIN
- # define LLONG_MIN (-LLONG_MAX-1)
- # endif
- # ifndef LLONG_MAX
- # define LLONG_MAX __LONG_LONG_MAX__
- # endif
- # ifndef ULLONG_MAX
- # define ULLONG_MAX (LLONG_MAX * 2ULL + 1)
- # endif
- #endif
- #ifdef __USE_POSIX
-
- # include <bits/posix1_lim.h>
- #endif
- #ifdef __USE_POSIX2
- # include <bits/posix2_lim.h>
- #endif
- #ifdef __USE_XOPEN
- # include <bits/xopen_lim.h>
- #endif
解釋:
(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。有一大堆兼容不同平臺的條件編譯宏,這對我們沒什么用,略去。
-
- typedef __PTRDIFF_TYPE__ ptrdiff_t;
-
- #if !(defined (__GNUG__) && defined (size_t))
- typedef __SIZE_TYPE__ size_t;
-
- #ifdef __BEOS__
- typedef long ssize_t;
- #endif
- #endif
- typedef _BSD_RUNE_T_ rune_t;
- #ifndef _RUNE_T_DECLARED
- typedef __rune_t rune_t;
- #define _RUNE_T_DECLARED
- #endif
- #ifndef __WCHAR_TYPE__
- #define __WCHAR_TYPE__ int
- #endif
- #ifndef __cplusplus /* C中才要定義wchar_t,C++中wchar_t為內置類型 */
- typedef __WCHAR_TYPE__ wchar_t;
- #endif
- #ifndef __WINT_TYPE__
- #define __WINT_TYPE__ unsigned int
- #endif
- typedef __WINT_TYPE__ wint_t;
- #ifndef __cplusplus
- #define NULL ((void *)0) /* C中定義NULL指針常量為(void*)0 */
- #else
- #define NULL 0 /* C++中定義NULL指針常量為0 */
- #endif
- #ifdef _STDDEF_H
-
- #define offsetof(TYPE, MEMBER) __builtin_offsetof (TYPE, MEMBER)
- #endif
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>。
-
-
- #ifndef _STDBOOL_H
- #define _STDBOOL_H
- #ifndef __cplusplus
- #define bool _Bool /* C中 */
- #define true 1
- #define false 0
- #else /* C++中 */
-
- #define _Bool bool
- #define bool bool
- #define false false
- #define true true
- #endif /* __cplusplus */
-
- #define __bool_true_false_are_defined 1
- #endif /* stdbool.h */
5、stdarg.h: 訪問可變參數表的類型和函數(用宏實現)。當你需要編寫有可變參數表的函數時,比如myfunc(int *a,...),你就可以用stdarg.h中的各個函數來遍歷“...”中的各個實參,以完成該函數的功能。略去沒有用的一大堆用于兼容不同平臺的條件編譯宏,如下:
-
- #ifndef __GNUC_VA_LIST
- #define __GNUC_VA_LIST
- typedef __builtin_va_list __gnuc_va_list;
- #endif
- #ifdef _STDARG_H
-
- #define va_start(v,l) __builtin_va_start(v,l)
-
- #define va_end(v) __builtin_va_end(v)
-
- #define va_arg(v,l) __builtin_va_arg(v,l)
- #if !defined(__STRICT_ANSI__) || __STDC_VERSION__ + 0 >= 199900L
-
- #define va_copy(d,s) __builtin_va_copy(d,s)
- #endif
- #define __va_copy(d,s) __builtin_va_copy(d,s)
- typedef __gnuc_va_list va_list;
-
-
- #endif /* _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中增加的。
-
- #ifndef _ISO646_H
- #define _ISO646_H
- #ifndef __cplusplus
- #define and &&
- #define and_eq &=
- #define bitand &
- #define bitor |
- #define compl ~
- #define not !
- #define not_eq !=
- #define or ||
- #define or_eq |=
- #define xor ^
- #define xor_eq ^=
- #endif
- #endif
之所以要為&&、|、!、^等這些運算符定義一個宏,是因為在ISO 646的字符集中要使用這些特殊的符號可能不方便,而用等價的宏名and、bitor、not、xor就比較方便了,在C++中這些宏名是關鍵字。C89增補1還提供了一些能在ISO 646中方便使用的字符來拼寫{、}之類的符號。如<%、%>、<:、:>、%:、%:%分別等價于字符{、}、[、]、#、##。