1. 程式人生 > 其它 >c/c++ 的記憶體位元組對齊

c/c++ 的記憶體位元組對齊

記憶體對齊

 

先了解下C/C++基本型別的位元組佔用情況,

 

 

alignas 關鍵字

 

 

 

#pragma pack(N)

pragma pack 規定的對齊長度,實際使用的規則是: 

  • 結構(如struct,union,或者class )內部的資料成員,第一個放在偏移為0的地方,以後每個資料成員的對齊,按照#pragma pack指定的數值和這個資料成員自身長度中,比較小的那個進行。 
  • 而結構之間整體的對齊,則按照結構中最大的資料成員 和 #pragma pack指定值 之間,較小的那個進行。

也就是說,當#pragma pack的值等於或超過所有資料成員長度的時候,這個值的大小將不產生任何效果。 

再來看個例子:

#include <iostream>
using namespace std;

#define OFFSET(struct_type, member) ((size_t) &((struct_type *) 0)->member)

#pragma pack(4)
struct TestB
{
    int aa;   // 第一個成員,放在[0,3]偏移的位置,
    char a;  // 第二個成員,自身長為1,#pragma pack(4),取小值,也就是1,所以這個成員按一位元組對齊,放在偏移[4]的位置。
    short b;  //第三個成員,自身長2,#pragma pack(4),取2,按2位元組對齊,所以放在偏移[6,7]的位置。 
char c; //第四個,自身長為1,放在[8]的位置。 }; #pragma pack() int main() { cout<< sizeof(TestB) << endl;    // 12 cout<< OFFSET(TestB, aa) <<endl;  // 0 cout<< OFFSET(TestB, a) <<endl;  // 4 cout<< OFFSET(TestB, b) <<endl;  // 6 cout<< OFFSET(TestB, c) <<endl;  //
8 }

上面的例子中,這個struct 實際佔據的記憶體空間是9位元組,但sizeof返回的是12,原因:

結構之間的對齊,是按照結構內部最大的成員的長度,和#pragma pack規定的值之中較小的一個對齊的。 
所以這個例子中,結構之間對齊的長度是min(sizeof(int), 4),也就是4。 4的倍數且比8(最後一個成員的偏移)大的是12,所以整個結構的佔用位元組數是12。

上面的例子中,去掉第一個int成員再來看:

#pragma pack(4)

struct TestB
{
//    int aa; 
    char a; // 第一個成員,放在[0]偏移的位置
    short b; // 第二個成員,自身長2,#pragma pack(4),取2,按2位元組對齊,所以放在偏移[2,3]的位置
    char c; //第三個,自身長為1,放在[4]的位置。

};

#pragma pack()

int main() {
    cout<< sizeof(TestB) << endl;   // 6
//  cout<< OFFSET(TestB, aa) <<endl;
    cout<< OFFSET(TestB, a) <<endl; // 0
    cout<< OFFSET(TestB, b) <<endl; // 2
    cout<< OFFSET(TestB, c) <<endl; // 4

}

這個例子中,結構之間對齊的長度是min(sizeof(short), 4),也就是2。 2的倍數且比4(最後一個成員的偏移)大的是6,所以整個結構的佔用位元組數是6。

 

換一種修改,只修改pragma的值,

#pragma pack(2)

struct TestB
{
    int aa; // 第一個成員,放在[0,3]
    char a; // 二個成員,自身長1,放在[4]
    short b; // 第三個成員,自身長2,#pragma pack(2),取2,按2位元組對齊,所以放在偏移[6,7]的位置
    char c; //第四個,自身長為1,放在[8]的位置。

};

#pragma pack()

int main() {

    cout<< sizeof(TestB) << endl;   // 10
    cout<< OFFSET(TestB, aa) <<endl;    // 0
    cout<< OFFSET(TestB, a) <<endl; // 4
    cout<< OFFSET(TestB, b) <<endl; // 6
    cout<< OFFSET(TestB, c) <<endl; // 8

}

 

 

更多例子:

有陣列的情況:

#pragma pack(4)

struct TestB
{
    float a[5];     // 第一個成員,放在[0, 19]
    char b;         // 第二個成員,自身長1,放在[20]
    float c[7];    // 第三個成員,自身長4,#pragma pack(4),取4,按4位元組對齊,所以放在偏移[24,51]的位置
    char d;         //第四個,自身長為1,放在[52]的位置。
};

#pragma pack()


int main() {

    cout<< sizeof(TestB) << endl;   // 56
    cout<< OFFSET(TestB, a) <<endl;    // 0
    cout<< OFFSET(TestB, b) <<endl; // 20
    cout<< OFFSET(TestB, c) <<endl; // 24
    cout<< OFFSET(TestB, d) <<endl; // 52

}

 

修改pack引數:

#pragma pack(2)

struct TestB
{
    float a[5];     // 第一個成員,放在[0, 19]
    char b;         // 第二個成員,自身長1,放在[20]
    float c[7];    // 第三個成員,自身長4,#pragma pack(2),取2,按2位元組對齊,所以放在偏移[22,49]的位置
    char d;         //第四個,自身長為1,放在[50]的位置。
};

#pragma pack()


int main() {

    cout<< sizeof(TestB) << endl;   // 52
    cout<< OFFSET(TestB, a) <<endl;    // 0
    cout<< OFFSET(TestB, b) <<endl; // 20
    cout<< OFFSET(TestB, c) <<endl; // 22
    cout<< OFFSET(TestB, d) <<endl; // 50

}

 

 

 

參考:

http://www.yebangyu.org/blog/2015/12/30/falsesharing/