1. 字节(byte)
字节的概念是明确的,字节是存储器中可寻址的最小单元,而且每个单元都是由一系列连续的比特组成。每个字节都对应一个存储器地址,该地址称为字节地址,简称地址。
我们平时遇到的平台,其字节的长度大多数是8比特的,即一个字节由8个二进制位组成。但在C标准的各版本中并没有定义字节的长度,它由实现而定。比如一些计算机的字节长度是9比特,C实现时就按9比特定义字节的长度。因此,在C中,一个字节所包含的比特数是由实现定义的,但要求不能少于8比特。C实现在头文件limits.h中定义了字节的长度,我们可以在该文件中找到宏CHAR_BIT,它的数值可能会随着平台的不同而不同,但无论如何它都是定义了一个字节所包含的比特数。
2. 对齐(alignment)
受硬件布线的限制,或者为了提高存储器访问效率,要求特定类型的对象在存储器中的位置只能开始于某些特定的地址,而该地址是 N 的倍数( N 是2的幂,不超过物理内存芯片可以提供的实际地址范围),我们就称该地址是 N 字节对齐(alignment)。当地址以二进制表示时,一个 N 字节对齐的地址最低位至少有
个位是零。例如:假设int的尺寸是4个字节,即按对齐要求,int类型的对象,可以位于0x08000004、0x08000008和0x0800000C等字节地址上,它们都是4的倍数,这些对象是对齐于4 的。long long int类型的对象(8字节),只能位于0x08000000、0x08000008、0x08000010、0x08000020等字节地址上,它们都是8的倍数。
当被访问的对象为 N 字节长且内存地址为N字节对齐时,称内存访问为对齐。当内存访问未对齐时,称为未对齐。注意,根据定义,字节内存访问总是对齐的,即 N 为1时,被访问的对象总是1字节对齐。
在C语言中,按照对齐来分配存储器时,各种类型的对象分配的内存的地址是不一样的。在Dev C++环境中,char对象是1字节对齐,int对象是4字节对齐,long long int对象是8字节对齐。
每个完整的对象类型都有一个称为对齐要求的属性,它是一个 size_t 类型的整数值,表示为该类型的对象分配的连续存储地址之间的字节数。有效的对齐值是 2 的非负整数幂。
一个类型的对齐要求可以用_Alignof(C11起)或 alignas查询。
为了满足结构中所有成员的对齐要求,可以在某些成员之后插入填充字节。
#include <stdio.h>
#include <stdalign.h>
struct S {
char a; // size: 1, alignment: 1
char b; // size: 1, alignment: 1
}; // size: 2, alignment: 1
// S.a和S.b可以分配到任何地址,因此,
// 结构S的对象可以分配到任何地址
struct X {
int n; // size: 4, alignment: 4
char c; // size: 1, alignment: 1
// 填充3个字节
}; // size: 8, alignment: 4
// 因为int的对齐要求(通常)是4,所以,
// X.n必须在4字节的边界上分配,
// 结构X的对象必须在4字节的边界上分配
int main(void)
{
printf("sizeof(struct S) = %zu\n", sizeof(struct S));
printf("alignof(struct S) = %zu\n", alignof(struct S));
printf("sizeof(struct X) = %zu\n", sizeof(struct X));
printf("alignof(struct X) = %zu\n", alignof(struct X));
}
程序的运行结果可能是:
每个对象类型都将其对齐要求强加给该类型的每个对象。任何类型中最严格(最大)的基本对齐是max_align_t的对齐。最小(弱)的对齐方式是char、 signed char和 unsigned char类型的对齐方式,对齐值等于1。
如果使用_Alignas使对象的对齐比max_align_t更严格(更大),则它具有扩展的对齐要求。成员具有扩展对齐的结构或共用体类型是过度对齐的类型(over-aligned types)。如果支持过度对齐的类型,且它们的支持在每种存储期中可能不同,则由实现定义(C11起)。