内存对齐 Shepard-Wang

一 内存对齐

1)为什么要内存对齐

尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的。

它一般会以双字节,四字节,8 字节,16 字节甚至 32 字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度。

假如没有内存对齐机制,数据可以任意存放,现在一个 int 变量存放在从 地址 1 开始的联系四个字节地址中,该处理器去取数据时,要先从 0 地址开始读取第一个 4 字节块,剔除不想要的字节(0 地址),然后从 地址 4 开始读取下一个 4 字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器。这需要做很多工作。

2)规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。gcc 中默认 #pragma pack(4),可以通过预编译命令 #pragma pack(n)n = 1,2,4,8,16 来改变这一系数。

有效对齐值:是给定值 #pragma pack(n) 和 结构体中最长数据类型长度 中 较小 的那个。有效对齐值也叫 对齐单位

了解了上面的概念后,我们现在可以来看看内存对齐需要遵循的规则:

  1. 结构体第一个成员的 偏移量(offset) 为 0,以后每个成员相对于结构体首地址的 offset 都是 该成员大小 与 有效对齐值 中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
  2. 结构体的总大小 为 有效对齐值 的 整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

数组类型的成员的对齐数按照数组的数据类型和默认对齐数取较小值,而不是用整个数组大小和默认对齐数取较小值

二 内存对齐练习题

判断下面结构体的大小:

VS 默认的对齐数是 8,32 位机器

1

struct S1
{
	char c1;
	int i;
	char c2;
};

解析:1(char) (+ 3(int 应该对齐到 4 的整数倍上,也就是 4,所以应该给 1 加上 3 凑成 4)) + 4(int) + 1(char) (+ 3最后整个结构体大小为最大对齐数(也就是 4)的整数倍处,所以结构体的大小不是 9 而是 12 )(最大对齐数是最大成员的对齐数,这个是前面算过的(成员大小和默认对齐数取小))

答案:12

2

struct S2
{
	char c1;
	char c2;
	int i;
};

第一个例题已经详细的分析了判断结构体大小的步骤,下面不再赘述。

1 (char)+ 1 (char) (+2) + 4 (int)

答案:8

3

struct S3
{
	double d;
	char c;
	int i;
}

8 (double) + 1 (char) (+3) + 4 (int)

答案:16

4

struct S3
{
	double d;
	char c;
	int i;
};

struct S4
{
	char c1;
	struct S3 s3;
	double d;
};

例 3 中,我们已经知道了 S3 的大小是 16

1 (char) (+ 7(结构体大小是 16 和 编译器默认对齐数 8 取较小值,所以结构体要对齐的整数倍是 8)) + 16 (S3) + 8 (double)

答案:32

不确定你可以自己在你的编译器上敲一下,看看运行结构,前提是编译器的默认对齐数是 8 ,如果不是,结果可能会不一样,那么编译器的默认对齐数可以修改吗?

修改默认对齐数

只需要加上一条指令即可:

#pragma pack(4)//设置默认对齐数为4

如果你想取消设置的默认对齐数,还原为默认:

#pragma pack()

C 的 _Alignof 和 C++ 的 alignof 可以获得类型的对齐。

https://zhuanlan.zhihu.com/p/30007037

二 memcmp 是否可以用来判断两个对象是否相同?

struct A
{
  char a;
  int b;
  char c;a
  double d;
};

问题 1: 结构体 A 的大小?

问题 2: 结构体的比较,怎么判断两个对象是否相等

问题 3memcmp 函数能不能用?为什么?

首先,如果默认对齐数为 8,则结构体的大小为 24 。如果不懂,可以我讲结构体内存对齐规则的文章:内存对齐

结构体的比较,如果我们直接这样写:

	struct A a, b;

	a == b;// error

这样肯定是不行的

我们可以重载 == 运算符:

	bool operator==(const A& rhs)
	{
		return (a == rhs.a) && (b == rhs.b)
			&& (c == rhs.c) && (d == rhs.d);
	}

现在就可以使用 a == b 来判断两个对象是否相等了。

我们先来看一下 memcmp 函数的描述:

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

Returns an integral value indicating the relationship between the content of the memory blocks:

return valueindicates
<0the first byte that does not match in both memory blocks has a lower value in ptr1 than in ptr2 (if evaluated as unsigned char values)
0the contents of both memory blocks are equal
>0the first byte that does not match in both memory blocks has a greater value in ptr1 than in ptr2 (if evaluated as unsigned char values)

简单来说,如果 ptr1 指向的内存与 ptr2 指向的内存前 num字节相同, memcmp 会返回 0

int main(void)
{
	struct A a, b;

	cout << (a == b) << endl;

	int flag = memcmp(&a, &b, sizeof(A));
	cout << flag << endl;
}
//输出:
// 1 0

这样来看,貌似使用 memcmp 也是可以的。真的如此吗?我们稍作改动:

int main(void)
{
	struct A a, b;

	cout << (a == b) << endl;

	// 将 a 的内存中第二个字节的内容改为字符 'b'
	*((char*)&a + 1) = 'b';
	cout << (a == b) << endl;

	int flag = memcmp(&a, &b, sizeof(A));
	cout << flag << endl;

}
// 输出:
// 1 1 -1

memcmp 返回 -1,表示 a < b 。但是改动后明明 a == b 返回的是真啊!

这是因为由于存在内存对齐的机制,对齐字节中的值我们不清楚。a == b 比较的是结构 A 中的 每个字段 ,而 memcmp比较的是 A 中的 每一个字节

我们不妨来看一下更改后 a 和 b 的内存布局:

&a:cc 62 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc
&b:cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc

如果要使用 memcmp 来比较,有两种办法:

  • 取消内存对齐机制(#pragma pack(1))
  • 使用内存前先初始化,将内存清零(memset

参考文章

深信服(C++软件开发工程师)面经

http://cplusplus.com/reference/cstring/memcmp/?kw=memcmp