14.2 对语言的补充

不同的编译器提供的库函数的数量是不同的。程序运行环境不同,所需要的库函数的数量也不同。在独立环境中运行的程序所需要的函数数量就比在宿主环境下所需要的函数数量要少。但有些库函数却是必需的。这些核心的库函数甚至可以看成是C语言的必要的扩展和补充,它们提供标准化的定义和标准化的参数设定(Standard Definetions and Parameterrization),以便使得C语言更具有可移植性。即使独立实现也必须提供这些库。本小节介绍与这些库有关的标准头文件。

14.2.1 标准定义stddef.h

这个标准头中的内容提供了标准库的一些常用定义,这些定义使得C语言程序更加容易移植。

奇怪的是,在Dev C++中“stddef.h”的内容是

14.2 对语言的补充 - 图1

其中的“#include_next<stddef.h>”实际上是“#include”的另一种形式的文件包含预处理命令。“_next<stddef.h>”是一个预处理单词,它指示真正的“stddef.h”在另一个位置。在真正的“stddef.h”中定义了以下两点。

(1)宏

NULL:表示空指针值的符号常量,通常就是“((void*)0)”。

offsetof(TYPE, MEMBER)((sire_t)&((TYPE *)O)—>MEMBER):这是一个计算关于结构体成员相对于结构体起始处位置的类似函数的宏。其第一个参数应是一个结构类型的名字,第二个参数应是结构成员名。例如

14.2 对语言的补充 - 图2

那么“offsetof(struct ex, c)”得到的是“(void )&s_ex.c-(void )& s_ex”的值。

这个例子告诉我们,结构体的成员在内存中不一定是连续的。C语言只保证(void )&s_ex=(void )&s_ex.i和结构体成员按顺序存放。

换句话说,结构体的尺寸一般大于等于各个成员尺寸之和。

(2)数据类型

size_t:sizeof运算符的结果类型,是某个unsigned整数类型。

ptrdiff_t:两个指针相减运算的结果类型,是某个signed整数类型。

wchar_t:宽字符类型。这种类型足以存放本系统所支持的所有本地环境中的字符集的编码值。这种类型后面还会谈到。

这里可以再次看到C语言对待各种基本数据类型的一种姿态。C语言并不事前把自己固定得很死,相反表现出了一种强大的灵活性和适应性。

14.2.2 iso646.h

这个头文件中规定了若干宏,这些宏用文字可以用来替代某些运算符。比如

14.2 对语言的补充 - 图3

这样(a<0&&a>10)可以用更文字化的方式写为(a<0 and a>10)。

这个头文件是C89在1995年进行技术修订时增补的(3),其内容不长,下面是其完整的内容。

14.2 对语言的补充 - 图4

14.2.3 limits.h和float.h

C标准并不包办代替一切,而是为各个编译器预留自己定义、自我发挥的空间。比如,“int”数据类型占据的内存空间及表示方法(补码、反码还是源码)等。各个编译器需要给出自己所定义的数据类型的特征。

毫无疑问,这两个标准头对程序的可移植性具有特殊重要的意义。

(1)limits.h

“limits.h”给出的是本编译器的各种整数类型的数据特征:最大值、最小值等。

例如,在Dev C++的“limits.h”中,给出的“int”数据类型的最大、最小值如下所示。

14.2 对语言的补充 - 图5

通常,C语言只规定“INT_MAX”这样的值至少应该是多少,但究竟取值多少是编译器自己决定的。

(2)float.h

“float.h”中给出的是描述浮点数类型特征的宏。这些宏描述了诸如浮点数的最大值、最小值、精度、计算时如何舍人、有效数字、最接近于0的值、浮点数差值的特征以及浮点数计算异常等。

本书不打算在近似数值计算方面多费笔墨。在这里想说的只有一句,数值计算并非像某些“主流教材”所写的那么简单。

14.2.4 stdarg.h

这个标准头的意义在于为编写参数数目不确定的函数提供一种标准方式。这可以保证代码具有很好的可移植性。

这个标准头的主要内容和用法已经在本书中做过介绍,这里不再重复。有一点需要补充的是,C语言规定,凡是与“…”对应的“float”类型的实参,都隐式地转换成“double”类型之后再进行函数调用。此外比“int”类型“小”的各种类型(比如“shor”,或“char”),总是转换成“int”类型,如果“int”类型的值无法表示那个“小”的unsigned类型所有的值,那么较“小”的那个被转换成“unsigned int”类型。

14.2.5 stdbool.h(C99)

这个标准头里的内容非常简单,不值得多说什么。

14.2 对语言的补充 - 图6

14.2.6 stdint.h(C99)

1.扩展整数类型

这个标准头的意义在于对整数类型进行扩展。

C语言的精神是让编译器自己最后确定数据类型的长度,比如“int”类型在不同的环境下就有不同的实现。

但是这会使得具有可移植性的代码更加难写。因此C99中提出了扩展整数类型的概念,其核心思想是直接规定确定长度的整数类型。例如“int16_t”就是一种长度为16位的“signed”整数类型。这种直截了当的方式无疑可以很好地解决可移植问题——不必再烦恼此环境下的某种类型对应的是彼环境下的何种类型了。

这样的类型有很多种,除了长度不同,它们在本质上是极其相似的。所以这里只选择一种——64位长度的整数类型进行介绍。

2.64位整数类型

(1)全定义或全不定义

C99并没有非常具体地规定应该定义哪些长度的扩展整数类型。但是一旦定义了某种长度的整数类型,就必须把这种长度的整数类型定义完整。比如编译器定义了64位的整数类型,那么相应的“signed”和“unsigned”类型都需要定义。此外还需要完整地定义所有关于这两种类型的宏。

(2)类型名称

这样的整数类型的名字都符合“intN_t”类型和“uintN_t”这样的格式,其中的“N”是一个十进制的正整数。

因此,64位的“signed”和“unsigned”整数类型的类型名字分别为“int64_t”和“uint64_t”。

关于值范围的宏和“limits.h”类似,“stdint.h”中也需要给出这两种类型的值的范围。不同的是,C99规定“signed”扩展类型必须使用补码。这样这两种类型的值域范围很容易知道:-263~263-1和0~264-1。在“stdint.h”中给出了值等于“_263”的宏(INT64_MIN)、值等于“263-1”的宏(INT64_MAX)以及值等于“264-1”的宏(UINT64_MAN)。

可以用这种类型的名字定义变量,例如

14.2 对语言的补充 - 图7

(3)常量的写法

写这种类型的常量是通过类似函数的宏完成的。不妨看一下Dev C++中这两种宏的定义。

14.2 对语言的补充 - 图8

显然这是通过加整数常量后缀实现的。

值得一看的还有16位整数类型常量宏的定义,如下所示。

14.2 对语言的补充 - 图9

这是通过显式类型转换实现的。

解决了常量的写法问题和变量定义问题,现在还剩下输入输出的问题没有解决。这个问题留在介绍“inttypes.h”部分时解决。

(4)最小长度类型和最快长度类型

前面的“int64_t”和“uint64_t”类型都属于“精确长度类型”(Exact-width Integer Types),意思是其类型的数据的长度恰好为64位,不多也不少。

C99还有“最小长度类型”(Minimum-width Integer Types)和“最快长度类型”(Fastest minimum-width Integer Types)。

前者的含义是不少于多少位,其“signed”和“unsigned”类型的名称分别为“int_least64_t”和“uint_least64_t”。其最大、最小值和“精确长度类型”一样,不需要再定义另外的宏。这种类型常量的写法也和“精确长度类型”相同(事实上前面两个宏展开的结果应该是“最小长度类型”的)。

“最快长度类型”的含义是保证最小长度前提下最快的一种类型。当然何种类型最快是由编译器判断选择的。“最快长度类型”的类型名字是“int_fastN”和“uint_fastN”,表示其值最大、最小值的宏名分别是“INT_FAST64_MIN”,“INT_FAST64_MAX”和“UINT_FAST64_MAX”。

(5)其他类型和其他的类型的范围

“stdint.h”中还给出“最大长度类型”(Greatest-width integer types)的定义,有些编译器可能还会提供一种长度和指针相同的整数类型(Integer types capable of holding object pointers)。表示这些类型的值的范围的宏定义将同时提供。

“stdint.h”也提供一些在其他标准头中定义的数据类型的表示值的范围的宏。比如“ptrdiff_t”、“wchar_t”、“wint_t”、“sig_atomic_t”和“size_t”等类型。