一、数据对齐/内存对齐
字、双字、四字在自然边界上不需要在内存中对齐。(什么叫做字?见第二部分)
对字、双字、四字来说,自然边界分别是偶数地址、可以被4整除的地址、可以被8整除的地址。
无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;然而,对齐的内存仅需要一次访问。
未对齐的:一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,需要两次总线周期访问内存。
对齐的:一个字起始地址是奇数但却没有跨越字边界被认为的对齐的,能够在一个总线周期中被访问。
缺省情况下,编译器默认将结构、栈中的成员数据进行内存对齐。
对齐的三个规则:
1. 每个成员分别按自己的方式对齐;
2. 复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式。(相当于把复杂类型展开)
3. 对齐后的长度必须是成员中最大的对齐参数的整数倍。(保证在处理数组时每一项都边界对齐)
例子
1. 对于如下结构
struct TestStruct1 { char c1; short 2; char c2; int I; };
假设c1的地址为0,则c1为00000000,s为00000002,c2为00000004,i为00000008。
2. 对于如下结构
struct TestStruct2 { char a; long b; }; struct TestStruct3 { char c; TestStruct2 d; long long e; };
TestStruct2中,成员a是1字节,默认按1字节对齐;成员b是4字节,默认按4字节对齐。所以sizeof(TestStruct2)为8.
TestStruct3中,成员c是按1字节对齐;d是个结构,它是8字节,但是结构的默认对齐方式是其所有成员使用的对齐参数中最大的一个,所以d是按4字节对齐;e是8字节,默认按8字节对齐。内存布局如下:(*表示空闲内存,1表示使用内存)
TestStruct3的内存布局: 1***,1***,1111,****,11111111
c d.a d.b e
另:对于typedef char Array3[3],Array3这种类型的对齐方式还是按1字节对齐,而不是按它的长度。
参考:《C语言深度剖析》第3章预处理,#pragma pack
#pragma pack(n) …//此中间的部分按指定对齐方式对齐 #pragma pack()
但是成员对齐有一个重要条件,即每个成员按自己的方式对齐。(也就是说,虽然指定了按n字节对齐,但并不是所有的成员都是以n字节对齐。)对齐规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中的较小的一个对齐,并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。
二、数据格式
由于是从16位体系结构扩展成32位的,Intel用术语“字”(word)表示16位数据类型。因为,32位数为“双字”(double words),64位数为“四字”(quad words)。
对比——字长
每台计算机都有一个字长(word size),指明整数和指针数据的标称大小。
因为虚拟地址是以这样的一个字来编码(这句话我的理解是,字长大小的各个位通过01变化来表示虚拟地址),所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。
也就是说,对于一个字长为w位的机器而言,虚拟地址的范围为0~2^w-1,程序最多访问2^w个字节。如今大多数计算机的字长都是32位,这就限定了虚拟地址空间为4GB字节。
C声明 |
char |
short |
int |
long int |
long long |
指针 |
float |
double |
32位机器 |
1 |
2 |
4 |
4 |
8 |
4 |
4 |
8 |
64位机器 |
1 |
2 |
4 |
8 |
8 |
8 |
4 |
8 |
可以看到,
- 短整数分配2字节,
- 不加限定的int为4字节,
- “长”整数使用机器的全字长(32位为4字节,64位为8字节)。
- ISO C99引入的“长长”整数使用8字节,即64位整数。(对于32位机器,编译器必须把这种数据类型的操作编译成执行一系列32位操作的代码。)
- 指针,使用机器的全字长。
- 对于浮点数,单精度和双精度分别使用4字节和8字节。
关于“字长”和“字”
字长,是指出计算机整数和指针数据的标称大小,可能是32位,也可能是64位等等;而,字指的就是16位,因为双字是32位、四字是64位。注意不要弄混“字长”和“字”的概念,“字长”的意思并不是”字“的长度,而是指计算机整数和指针数据的标称大小。