4.2 基本数据类型和内存映像
数据类型用来定义变量的值的类型,每种数据类型对应特定的字节数。例如,在32位操作系统上,int类型的变量就拥有4字节的内存单元,而double类型的变量占据8字节的内存单元。
【提示4-2】: 字节是内存编址的最小单位。因为语言必须支持对一个变元(基本类型或复合类型的变量或对象)进行取地址运算(&),而且这个地址必须是有效的内存单元的地址,所以最小的对象(包括空对象)也至少会占据1字节的内存空间。请使用sizeof()确定数据类型在不同系统上的大小。
标准C语言支持的基本(内建)数据类型有int、long、float、double、char、void,以及它们和signed、unsigned、*、&等的组合(有些组合是不支持的,如void&)。
标准C++在这些类型的基础上增加了bool类型,并同时增加了两个内置的符号常量TRUE和FALSE(关键字)。
void是“空”类型(无值型),意思是这种类型的大小无法确定。显然不存在void类型的对象,所以你也就不能声明void类型的对象或是将sizeof()运算符用于void类型,C++/C语言不能对一个大小未知的对象直接操作。void通常用来定义函数的返回类型、参数列表(无参)或者void指针。void指针可以作为通用指针,因为它可以指向任何类型的对象。
【提示4-3】: 注意要分清void类型指针和NULL指针值之间的区别。NULL是可以赋值给任何类型指针的值0,在C语言环境中它的类型为void*,而在标准C++语言环境中由于允许从0到任何指针类型的隐式转型,因此NULL就是整数0。即:
#ifdef __cplusplus #define NULL 0 #else #define NULL ((void*)0) #endif
一个void*类型的指针是一个合法的指针,常用在函数参数中来传递一个函数与其调用者之间约定好类型的对象地址,如在线程函数中。而一个值等于NULL的指针虽也是一个合法的指针,但不是一个有效的指针。
虽然bool类型的变量只存在两种可能的值:true和false,按理说只需要一个bit就可以表示了。但是字节是内存编址的最小单位,而计算机从内存中提取一个变量的值是通过其地址进行的,所以一个bool变量也占据1字节内存,即sizeof(bool)等于1,浪费了7 bit。
标准C语言中没有bool类型,但是某些实现通过库提供了其映射,并且定义了相应的常量。例如:
typedef int BOOL; #define TRUE 1 #define FALSE 0
在标准C中,int为默认类型,也就是说如果你不明确指定函数的形参类型或函数的返回值类型,则它们的类型为int。标准C++不支持默认类型,但是在模板中有“默认类型参数”的概念。
【提示4-4】: 无论是C还是C++程序,都不要使用默认数据类型。一定要明确指出函数每一个形参的类型和返回值类型。
某些基于RISC(精简指令集计算机)的CPU,如SPARC、PowerPC等,对内存中基本数据类型的变量采用高字节(BYTE)和高字(WORD)在低地址存放、低字节和低字在高地址存放的Big Endian存储格式(即高字节、高字在前,或地址大的字节结尾),并且把最高字节的地址作为变量的首地址。在这种自然的存储格式中,要求变量在内存中的存放位置必须自然对齐,否则CPU会报告异常。所谓自然对齐,就是基本数据类型(主要是short、int和double)的变量不能简单地存储于内存中的任意地址处,它们的起始地址必须能够被它们的大小整除。例如:在32位平台下,int和指针类型变量的地址应该能被4整除,而short变量的地址都应该是偶数,bool和char则没有特别要求。所以,基于这种CPU架构的平台,编译器将按照自然对齐的要求来为每个变量生成逻辑地址,C++/C编译器亦如此。例如,short型变量x和int型变量y的内存布局及其首地址如图4-1所示。
图4-1 Big Endian和自然对齐
Intel系列CPU采用Little Endian存储格式来存放基本类型变量,即低字节和低字在低地址存放、高字节和高字在高地址存放(即低字节、低字在前,或地址小的字节结尾),并且把最低字节的地址作为变量的首地址。在这种硬件平台上,上述两个变量x和y在内存中的布局将如图4-2所示。
图4-2 Little Endian和自然对齐
在Intel系列CPU这种硬件平台上,并不要求基本类型变量在内存中必须自然对齐,同样也不会要求复合类型变量必须自然对齐。如果变量没有自然对齐,可能会在一定程度上降低CPU访问该变量的性能,但并不会影响程序的正确性。
对于任意类型的数组,我们知道下面的断言都应该是真的:
SomeType x,y[100]; assert(sizeof(y)==sizeof(x)*100); //true
编译器必须确保不仅第一个对象元素要自然对齐,而且以后的每一个对象元素也要对齐才行,所以数组的自然对齐要求和单个元素的对齐要求是一样的。
关于复合数据类型的对齐问题,我们将在本书8.1.4节详细讲解。