虽说C语言是一门很成熟的编程语言,但是近些年来也是有所发展的,从早期的C89到后来的C99、C11等新标准,C语言逐步增加了许多好用的功能,例如新标准头文件“stdint.h”的添加。
C语言逐步增加了许多好用的功能
stdint.h头文件
如果程序员希望写出可移植的C语言程序,首先最重要的一点是不能假定位宽。C语言标准并没有明确指定的short、int、long等类型的位宽,因此可能在某些平台sizeof(int)等于2,在其他平台sizeof(int)等于4,所以如果编写的C语言代码假定sizeof(int)是一个固定值,显然就属于不可移植的代码。
为了解决这样的问题,在新标准文件“stdint.h”之前,程序员必须做些额外的工作,以确定C语言代码运行的平台的各种数据位宽,这样的工作着实烦人,稍不留神就会出错。
“stdint.h”头文件的添加就是为了便于程序员写出不假定位宽的程序的。其内部通过typedef和宏判断定义了不少好用的整数类型,例如int8_t类型表示8位的有符号的整数类型,uint32_t则表示32位的无符号的整数类型。类似的,还有int16_t、int64_t、uint64_t等类型,都是比较好理解的。
C语言中的“快”类型
不过,如果读者打开stdint.h头文件,应该能够看到一些更有趣的类型,如下图:
更有趣的类型
可以看出,这些类型被称作“fasttype”,类型名中也有fast的字样(如int_fast16_t),直译成中文即“快类型”,那么它们到底有什么含义呢?
观察力敏锐的读者应该发现了,int_fast16_t和int_fast32_t其实是一样的,以上图*框为例,它们都表示int类型,这是怎么回事呢?int类型怎么能同时表示16位宽和32位宽的整数类型呢?
int类型当然不能同时表示两种位宽的整数类型,事实上,int_fastxx_t类型并不是准确的xx位宽类型,它表示不低于xx位宽的类型,因此,只要int的位宽大于或者等于32位,它就能同时表示int_fast16_t和int_fast32_t类型。
有读者看到这里可能会有疑问,如果int的位宽等于32,那么使用它来表示16位宽的int_fast16_t整数类型,不是造成资源浪费了吗?
不是造成资源浪费了吗?
其实读者应将注意力放在“fast”一词上。CPU从内存取数据一般是逐字取得,这里的“字”并不是字节的意思,在32位主机上,字长常常是4个字节,也即CPU单次取出数据的最小单位是4个字节。
字长也可以理解为CPU读取数据的“步长”。
也就是说,CPU读取数据的“步长”是字长(下文以4字节为例),也就是说假设这次读取了地址0~3的数据,接下来若是希望读取相邻的数据,最接近的地址也得是地址4~7。其实从这里可以看出,CPU每次读取数据的起始地址都是字长的整数倍。
这也是“数据对齐”的原因——为了CPU读取数据的效率。
如果需要读取的数据只有一个字节(char型),那么显然,无论该字节放在哪里,它总是在某个字长段的范围内的(例如地址0~3,地址4~7内),此时CPU一次就能读取完毕。
CPU一次就能读取完毕。
如果需要读取的数据有两个字节(16bits),情况就不同了——它的地址可能是3~4,而CPU读取数据的“步长”是4个字节,若要读取该值,CPU只能先读取0~3字节的数据,再读取4~7字节的数据,最后还需要组合拼凑,才能得到该值。这样的一系列操作显然非常低效。
所以stdint.h将“快”类型定义为字长的整数倍的意图就一目了然了,无非就是牺牲一些“空间”换取“时间”。当然了,读者在使用“快类型”时需要注意:int_fast16_t并不一定恰好是16位宽,它只是不少于16位宽的类型。
C语言中的“小”类型
前面提到,int_fastxx_t类型牺牲了“空间”换取“时间”,如果在某个C语言项目中,“空间”效率并不是特别重要,而“空间”效率却非常重要,那么int_fastxx_t类型显然就不合适了。此时可以使用stdint.h中定义的“smalltype”,也即“小类型”。
C语言中的“小类型”
int_leastxx_t系列的数据类型基本上保证了其恰好是xx位宽,避免了空间浪费,但是按照前文的分析,“小类型”付出的代价是损失了一部分时间效率。
小结
本文主要讨论了C语言新增头文件“stdint.h”中定义的几种整数类型,并在此基础上讨论了“时间”效率和“空间”效率的矛盾。事实上,stdint.h头文件中还定义了其他一些好用的宏,比如整数指针,各个数据类型的最大值和最小值等等,留给读者自己查看了。
点个