Post

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的倍数。

结构对齐的规则

结构体内存对齐的规则是比较复杂的,但可以概括为以下几个主要原则:

  1. 基本对齐规则:

    • 每个成员变量的起始地址必须是其自身大小的整数倍。
    • 整个结构体的大小必须是其最大成员变量大小的整数倍。
  2. 结构体对齐值:

    结构体的对齐值是其成员中要求最严格对齐的类型的大小。通常是最大基本类型成员的大小。

  3. 成员排列:

    • 第一个成员放在偏移量为0的地方。
    • 后面每个成员的对齐值是该成员大小与结构体对齐值中的较小者。
  4. 填充:

    • 如果下一个成员的起始位置不满足其对齐要求,编译器会在当前位置和下一个成员之间插入填充字节。
    • 在结构体的最后,可能会添加填充以使整个结构体的大小是结构体对齐值的整数倍。
  5. 嵌套结构体:

    • 嵌套的结构体会按其自身的对齐要求对齐。
    • 包含嵌套结构体的外部结构体可能因此需要额外的填充。
  6. 位域(Bit fields):

    • 位域的对齐规则可能因编译器而异。
    • 通常,包含位域的结构体会按照未使用位域时的规则对齐。
  7. 编译器特定行为:

    • 不同编译器可能有轻微不同的对齐策略,特别是在处理位域时。
    • 某些编译器可能会为了优化而重新排序成员(除非明确禁止)。
  8. 平台相关性:

    • 对齐规则可能因目标平台(32位/64位)而异。
  9. 特殊指令:

    • 可以使用 #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)) 都是用来控制结构体内存对齐的指令,但它们的使用方式和适用范围略有不同。让我们详细了解一下它们的作用:

  1. #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 字节,没有任何填充。

  2. __attribute__((packed))

    这是 GCC 和 Clang 等编译器支持的特性,用于个别结构体的紧凑布局。

    • 语法:将 __attribute__((packed)) 放在结构体定义的末尾。
    • 作用范围:仅适用于被修饰的单个结构体。
    • 功能:完全取消内存对齐,结构体成员会紧密排列,没有任何填充。

    例如:

    1
    2
    3
    4
    5
    
    struct Example {
        char a;
        int b;
        short c;
    } __attribute__((packed));
    

    这个 Example 结构体的总大小将是 7 字节,成员之间没有填充。

这两种方法的主要作用是:

  1. 减少内存使用:通过减少或消除填充,可以使结构体更加紧凑。

  2. 控制数据布局:在需要精确控制内存布局的场景中很有用,比如与硬件通信或处理特定格式的二进制数据。

  3. 跨平台兼容性:在不同系统间传输数据时,确保数据结构的一致性。

  4. 优化特定场景:在某些情况下,紧凑的数据结构可能会提高缓存效率。

然而,使用这些指令时需要注意:

  • 可能会降低访问效率:未对齐的内存访问在某些架构上可能会很慢。
  • 可能导致硬件异常:某些处理器可能不支持未对齐的内存访问。
  • 可能影响可移植性:不同编译器对这些指令的支持可能不同。

因此,虽然这些指令在特定场景下很有用,但应谨慎使用,并充分考虑性能和兼容性的影响。

This post is licensed under CC BY 4.0 by the author.

Trending Tags