align
今天看到了一个比较神奇的宏定义,关于字节对齐的,转换成比较能理解的语言就如下了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;
// x 对 a 对齐,先 + ,之后利用低位取0
int align(int x,int a){
int add = (x + a - 1); // 类似于ceiling的操作,(x + a - 1) / a
int lowzero = ~(a - 1);// 此时,最低位1之后的为全0,之前的最低位1不变
return (x + a - 1) & (~(a - 1));
}
int main(){
int x,a;
cin >> x >> a;
cout << align(x,a) << endl;
return 0;
}
这里先做一个上取整+,很类似于之前那个ceiling的操作。
1
2
3
int ceil(int x,int a){
return (x + a - 1) / a;
}
之后的字节对齐就比较显眼了,因为a确定只有一个1,要么32,要么16,要么8,所以让最低位之后的变0,高位全1就ok了.因为之前做了一个上取整的操作,之后得到的数一定是一个a的倍数。
结构对齐的规则
结构体内存对齐的规则是比较复杂的,但可以概括为以下几个主要原则:
基本对齐规则:
- 每个成员变量的起始地址必须是其自身大小的整数倍。
- 整个结构体的大小必须是其最大成员变量大小的整数倍。
结构体对齐值:
结构体的对齐值是其成员中要求最严格对齐的类型的大小。通常是最大基本类型成员的大小。
成员排列:
- 第一个成员放在偏移量为0的地方。
- 后面每个成员的对齐值是该成员大小与结构体对齐值中的较小者。
填充:
- 如果下一个成员的起始位置不满足其对齐要求,编译器会在当前位置和下一个成员之间插入填充字节。
- 在结构体的最后,可能会添加填充以使整个结构体的大小是结构体对齐值的整数倍。
嵌套结构体:
- 嵌套的结构体会按其自身的对齐要求对齐。
- 包含嵌套结构体的外部结构体可能因此需要额外的填充。
位域(Bit fields):
- 位域的对齐规则可能因编译器而异。
- 通常,包含位域的结构体会按照未使用位域时的规则对齐。
编译器特定行为:
- 不同编译器可能有轻微不同的对齐策略,特别是在处理位域时。
- 某些编译器可能会为了优化而重新排序成员(除非明确禁止)。
平台相关性:
- 对齐规则可能因目标平台(32位/64位)而异。
特殊指令:
- 可以使用
#pragma pack
或__attribute__((packed))
等指令来改变默认对齐行为。
- 可以使用
示例:
1
2
3
4
5
6
struct Example {
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
double d; // 8 bytes
};
在大多数64位系统上,这个结构体的内存布局可能是:
1
2
3
4
5
6
a: 1 byte
3 bytes padding
b: 4 bytes
c: 2 bytes
6 bytes padding
d: 8 bytes
总大小为24字节(8的倍数,因为最大成员是8字节的double)。
pack的方法
#pragma pack
和 __attribute__((packed))
都是用来控制结构体内存对齐的指令,但它们的使用方式和适用范围略有不同。让我们详细了解一下它们的作用:
#pragma pack
这是一个预处理指令,用于改变默认的内存对齐方式。
- 语法:
#pragma pack(n)
其中 n 通常是 1, 2, 4, 8 或 16。 - 作用范围:从这条指令之后的所有结构体定义,直到遇到
#pragma pack()
或文件结束。 - 功能:强制结构体按照 n 字节对齐,而不是默认的对齐方式。
例如:
1 2 3 4 5 6 7
#pragma pack(1) struct Example { char a; int b; short c; }; #pragma pack()
这会使
Example
结构体按 1 字节对齐,总大小为 7 字节,没有任何填充。- 语法:
__attribute__((packed))
这是 GCC 和 Clang 等编译器支持的特性,用于个别结构体的紧凑布局。
- 语法:将
__attribute__((packed))
放在结构体定义的末尾。 - 作用范围:仅适用于被修饰的单个结构体。
- 功能:完全取消内存对齐,结构体成员会紧密排列,没有任何填充。
例如:
1 2 3 4 5
struct Example { char a; int b; short c; } __attribute__((packed));
这个
Example
结构体的总大小将是 7 字节,成员之间没有填充。- 语法:将
这两种方法的主要作用是:
减少内存使用:通过减少或消除填充,可以使结构体更加紧凑。
控制数据布局:在需要精确控制内存布局的场景中很有用,比如与硬件通信或处理特定格式的二进制数据。
跨平台兼容性:在不同系统间传输数据时,确保数据结构的一致性。
优化特定场景:在某些情况下,紧凑的数据结构可能会提高缓存效率。
然而,使用这些指令时需要注意:
- 可能会降低访问效率:未对齐的内存访问在某些架构上可能会很慢。
- 可能导致硬件异常:某些处理器可能不支持未对齐的内存访问。
- 可能影响可移植性:不同编译器对这些指令的支持可能不同。
因此,虽然这些指令在特定场景下很有用,但应谨慎使用,并充分考虑性能和兼容性的影响。