|
对齐(alignment)
受硬件布线的限制,或者为了提高存储器访问效率,要求特定类型的对象在存储器里的位置只能开始于某些特定的字节地址,而这些字节地址都是某个数值N的特定倍数(以不超过实际的存储空间为限),这称为对齐(alignment)。进一步地,我们称那个对象是对齐于N的。
举一个实际的例子,在编者的计算机上,int类型的对象,可以位于0x04、0x08、0x0C等字节地址上,都是4的倍数(但不能超过物理内存芯片可以提供的实际地址范围);long long int类型的对象,只能位于0x08、0x10、0x18、0x20等任何一个字节地址上,都是8的倍数(但不能超过物理内存芯片可以提供的实际地址范围)。
再比如,char类型的对象可以位于任何字节地址上,如0x01、0x02、0x03等,都是1的倍数(但不能超过内存的实际地址范围)。
细心的读者可能会发现这里没有提到字节0x00000000。在C中,这是一个特殊的字节地址,任何对象都不能起始于这个位置。
对于完整的对象类型来说,“对齐”限制了它在存储器中可以被分配到的地址。实际上,这是一个由C实现定义的整数值。
未对齐的存储器访问对不同的计算机来说会有不同的效果。在有些硬件架构上(比如Intel x86系列),未对齐的访问不会引发实质性的问题,也不会影响结果的正确性,但会使处理器对存储器的访问变得笨拙;在另一些硬件架构上,未对齐的访问将导致总线错误。
C11引入了两个关键字“_Alignas”和“_Alignof”,_Alignas作用于指定的类型,或者某个类型的对象,只在声明中使用,强制它们按要求对齐。
- int _Alignas (8) foo;
- struct s {int a; int _Alignas (8) bar;};
复制代码
以上将使int类型的对象foo和结构类型的成员bar按8字节对齐。
对齐的数值可以通过_Alignof运算符得到。_Alignof运算符的操作数要求是用括号括起来的类型名,例如:
- # include <stdio.h>
- void f (void)
- {
- printf ("%zu, %zu, %zu, %zu\n",
- _Alignof (char),
- _Alignof (int),
- _Alignof (int [33]),
- _Alignof (struct {char c; float f;}) );
- }
复制代码
运算符_Alignof的结果类型是size_t,是在头文件<stddef.h>中定义的一个无符号整数类型。_Alignof返回的是一个有效的基础对齐(参见“基础对齐”)。但有效的对齐也包括可能存在的扩展对齐(参见“扩展对齐”)。
值得注意的是,对齐并不是两个连续分配的同类型对象的地址所间隔的字节数,尽管有时候它们的确一样。类型的大小和对齐没有必然联系。
比如,在编者的机器上,int类型的对齐是4,这种类型的对象只能位于字节地址0x00000004、0x00000008、0x0000000C,……,两个连续的字节地址间隔为4,和它们的对齐相等。但是,再看下面的例子:
- # include <stdio.h>
- void f (void)
- {
- typedef struct {char a [13]; int i;} A;
- printf ("%zu, %zu\n", sizeof (A), _Alignof (A));
- }
复制代码
在编者的机器上,类型A的大小是20,对齐是4,等于int类型的对齐。类型A的对象同样可以位于字节地址0x04、0x08、0x0C,……,因为int类型的对齐更为严格,所以,只要保证结构成员i的字节地址能够被4整除即可。如果类型A的某个对象位于字节地址0x04,则该对象的成员i则位于0x14;如果类型A的某个对象位于字节地址0x08,则该对象的成员i则位于0x18。但这种类型的对象较大,如果要在存储器中连续分配多个这样的对象,则它们的地址只能分别起始于0x04、0x18、0x2C,……,相互间隔的字节数并不等于类型A的大小。
无论如何,所有对象的对齐都要求必须是2N,N为非负整数。
要判断两个对象是否具有相同的对齐,只需要看它们的数值。数值上相等的,对齐也相同;数值上不相等的,对齐也不同;如果数值较大,则表示它的对齐更严格;如果数值较小,则表示它的对齐较弱。
不同的对象,按其类型不同有不同的对齐要求。如果对它们进行比较,则可以分为3种:弱对齐、强对齐和严格对齐。它们在数值上由小到大排列,弱对齐的适应性较好,具有弱对齐的对象,也可以位于(对齐于)强对齐和严格对齐的地址上。
进一步地,char、signed char和unsigned char类型的对象具有最弱的对齐要求。
|
|