C++資料型別對齊、對齊規則
C++元素對齊探討
探討內容與目標
探討C++元素的對齊方式,並以sizeof()型別返回值,測試是否理解正確。
重點留意後面的結果分析,有經驗總結哦!!!
操作環境
測試環境為Windows 10,Visual Studio 2015.
一些基本知識
- 各元素型別的sizeof,以及其相應的對齊大小需要掌握。
- data alignment 資料對齊
- data padding 資料填充
- 區分3個概念:結構體對齊大小,基本元素對齊大小,預設對齊大小
結構體對齊大小,是align(A)之後得到的大小,是本次討論重點研究的物件,也是sizeof()以及padding的最終因素。本次討論一般設為x
基本元素對齊大小,就是int之類的預設對齊大小,一般而言,就是基本元素的位元組數。它們以一個整體出現的話,就不受#pragma pack()指令的影響。如果,基本元素在結構體內的話,受#pragma pack()影響。本次討論,談及基本元素對齊大小都是指的前者。本次討論一般設為y。
預設對齊大小,就是#pragma pack()指令的預設值,一般為1,2,4,8,16。本次討論一般設為z。
本次研究的主題,其實質就是x = f(y1, ... ,yn, z)
的函式關係。
- 一些函式介紹
- align() //計算元素型別對齊大小
align()用於計算某個元素型別的對齊大小x
比如:
struct A
{
char a;
int b;
short c;
};
cout << alignof(A) << endl; //ok 4
A a;
cout<<alignof(a)<<endl; //error
#pragma pack()
//設定預設對齊大小
#pragma pack()
是編譯器的預處理命令
可用的預設對齊大小是1、2、4、8(預設)、16;
相應的設定方法- 專案->屬性->配置屬性->c/c++->程式碼生成->結構成員對齊->進行設定
- 呼叫
#pragma pack(n)
。eg:#pragma pack(4); //設定預設的對齊大小為4
#pragma pack() //設定預設對齊大小為預設值, 一般為8
#pragma pack(4) //設定預設對齊大小為4
- offsetof(type, var) //求type內的var的起始地址相對於type的起始地址的偏移量
struct A
{
char a;
int b;
short c;
};
//計算結構體元素a的起始地址相對於A的起始地址的偏移量
cout << offsetof(A, a) << endl; //ok 0
cout << offsetof(A, b) << endl; //ok 4
測試程式碼
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
struct A1
{
char c;
};
struct A2
{
char c1;
char c2;
int a;
};
struct A3
{
char c1;
int a;
char c2;
};
struct A4
{
};
struct A5
{
char c;
double d;
char c1[7];
};
//設定預設對齊大小為2
#pragma pack(2)
struct A6
{
char a;
int b;
//short c;
};
struct A7
{
char b;
};
#pragma pack(4)
struct A8
{
A6 c1;
A7 d1;
};
//恢復預設預設大小--8
#pragma pack()
struct A9
{
char c;
struct A9_inner
{
short ss;
}a9in;
double d;
};
struct A10
{
char c;
struct A10_inner
{
short ss;
double dd;
}a10in;
double d;
};
//即使宣告預設對齊大小為2,int位域還是進行32位擴充套件,而不是16位
#pragma pack(2)
struct A11
{
short s;
char c;
int a1 : 1;
int a2 : 4;
int a3 : 7;
};
#pragma pack()
struct A12
{
short s;
char c;
long long a1 : 1;
long long a2 : 4;
long long a3 : 7;
};
//位域超過一個整體時,首次超過的那個位域欄位需要考慮對齊
struct A13
{
unsigned a : 19;
unsigned b : 11;
//19+11+4 = 34 > 32 因此,c應該在下一個int裡面
unsigned c : 4;
//同理,4 + 29 = 33 > 32 存在偏移
unsigned d : 29;
char index;
};
class Base
{
};
class C1
{
char c;
Base b;
};
class C2
{
char c;
Base b;
int a;
};
class C3 :public Base
{
char c;
};
class Base1
{
char c;
int a;
};
class C4 :public Base1
{
char c1;
double d;
};
class C41
{
Base1 b;
char c1;
double d;
};
class C5 :public Base1
{
char c1;
int a;
double d;
};
class C51
{
char c1;
Base1 b;
int a;
double d;
};
class Base2
{
public:
virtual ~Base2(){ }
};
class C6
{
char c;
Base2 b;
};
class C7
{
char c;
Base2 b;
int a;
};
class C8 :public Base2
{
char c;
};
class Base3
{
char c;
public:
virtual ~Base3()
{
}
};
class C9 :public Base3
{
char c1;
public:
virtual void print(){ }
};
class Base4
{
public:
virtual void print() = 0;
};
class C10 :public Base4
{
char c;
public:
virtual void print()
{
}
};
class Base5
{
char c;
public:
virtual void print() = 0;
};
class C11 :public Base5
{
char c1;
public:
virtual void print()
{
}
};
//測試開始標記
static void printStart()
{
cout << "--------------------- test ---------------------\n";
}
//測試退出標記
static void printEndLine()
{
cout << "--------------------- end ---------------------\n";
}
//測試基本元素的位元組數和對齊大小
static void testBaseElement()
{
printStart();
cout << "char: " << sizeof(char) << "---" << alignof(char) << endl;
cout << "int: " << sizeof(int) << "---" << alignof(int) << endl;
cout << "short: " << sizeof(short) << "---" << alignof(short) << endl;
cout << "long: " << sizeof(long) << "---" << alignof(long) << endl;
#pragma pack(2)
//基本型別為一個整體時,不受預設對齊大小的約束
cout << "long long: " << sizeof(long long) << "---" << alignof(long long) << endl;
#pragma pack()
cout << "float: " << sizeof(float) << "---" << alignof(float) << endl;
cout << "double: " << sizeof(double) << "---" << alignof(double) << endl;
cout << "long double: " << sizeof(long double) << "---" << alignof(long double) << endl;
void* p = NULL;
cout << "pointer: " << sizeof(p) << "---" << alignof(char*) << endl;
printEndLine();
}
//測試struct相關的位元組數和對齊大小
static void testOnlyWithStruct()
{
printStart();
cout << "struct A1: " << sizeof(A1) << "---" << alignof(A1) << endl;
cout << "struct A2: " << sizeof(A2) << "---" << alignof(A2) << endl;
cout << "struct A3: " << sizeof(A3) << "---" << alignof(A3) << endl;
cout << "struct A4: " << sizeof(A4) << "---" << alignof(A4) << endl;
cout << "struct A5: " << sizeof(A5) << "---" << alignof(A5) << endl;
cout << "struct A6: " << sizeof(A6) << "---" << alignof(A6) << endl;
cout << "struct A6.b: " << sizeof(A6::b) << "---" << offsetof(A6, b) << endl;
cout << "struct A7: " << sizeof(A7) << "---" << alignof(A7) << endl;
cout << "struct A8: " << sizeof(A8) << "---" << alignof(A8) << endl;
cout << "struct A9: " << sizeof(A9) << "---" << alignof(A9) << endl;
cout << "struct A10: " << sizeof(A10) << "---" << alignof(A10) << endl;
cout << "struct A11: " << sizeof(A11) << "---" << alignof(A11) << endl;
cout << "struct A12: " << sizeof(A12) << "---" << alignof(A12) << endl;
cout << "struct A13: " << sizeof(A13) << "---" << alignof(A13) << endl;
printEndLine();
}
//測試不存在虛擬函式的類的位元組數和對齊大小
static void testWithClassNonVirtual()
{
cout << "class Base: " << sizeof(Base) << "---" << alignof(Base) << endl;
cout << "class C1: " << sizeof(C1) << "---" << alignof(C1) << endl;
cout << "class C2: " << sizeof(C2) << "---" << alignof(C2) << endl;
cout << "class C3: " << sizeof(C3) << "---" << alignof(C3) << endl;
cout << "class Base1: " << sizeof(Base1) << "---" << alignof(Base1) << endl;
cout << "class C4: " << sizeof(C4) << "---" << alignof(C4) << endl;
cout << "class C41: " << sizeof(C41) << "---" << alignof(C41) << endl;
cout << "class C5: " << sizeof(C5) << "---" << alignof(C5) << endl;
cout << "class C51: " << sizeof(C51) << "---" << alignof(C51) << endl;
}
//測試不存在虛擬函式的類的位元組數和對齊大小
static void testWithClassVirtual()
{
cout << "class Base2: " << sizeof(Base2) << "---" << alignof(Base2) << endl;
cout << "class C6: " << sizeof(C6) << "---" << alignof(C6) << endl;
cout << "class C7: " << sizeof(C7) << "---" << alignof(C7) << endl;
cout << "class C8: " << sizeof(C8) << "---" << alignof(C8) << endl;
cout << "class Base3: " << sizeof(Base3) << "---" << alignof(Base3) << endl;
cout << "class C9: " << sizeof(C9) << "---" << alignof(C9) << endl;
//alignof(Base4),alignof(Base5) 會報錯, 不能例項化抽象類
cout << "class Base4: " << sizeof(Base4) << "---" << endl;
cout << "class C10: " << sizeof(C10) << "---" << alignof(C10) << endl;
cout << "class Base5: " << sizeof(Base5) << "---" << endl;
cout << "class C11: " << sizeof(C11) << "---" << alignof(C11) << endl;
}
//測試類相關的位元組數和對齊大小,主要涉及繼承和虛擬函式
static void testWithClass()
{
printStart();
testWithClassNonVirtual();
cout << "\n------------------------------------------------\n" << endl;
testWithClassVirtual();
printEndLine();
}
struct D1
{
int size;
//使用了非標準擴充套件;結構/聯合中的零大小陣列
//這是C99的柔性陣列Flexible Array,也就是變長陣列
char data[0]; //或者char data[];
};
struct D2
{
//使用了非標準擴充套件;結構/聯合中的零大小陣列
char data[0];
};
//不能直接定義一個數組大小為0的陣列
//char data[0];
//測試柔性陣列
static void testOther()
{
printStart();
cout << "class D1: " << sizeof(D1) << "---" << alignof(D1) << endl;
cout << "class D2: " << sizeof(D2) << "---" << alignof(D2) << endl;
printEndLine();
}
int main()
{
freopen("out64.txt", "w", stdout);
testBaseElement();
cout << endl;
testOnlyWithStruct();
cout << endl;
testWithClass();
cout << endl;
testOther();
return 0;
}
測試結果
- 32位編譯器結果
--------------------- test ---------------------
char: 1---1
int: 4---4
short: 2---2
long: 4---4
long long: 8---8
float: 4---4
double: 8---8
long double: 8---8
pointer: 8---8
--------------------- end ---------------------
--------------------- test ---------------------
struct A1: 1---1
struct A2: 8---4
struct A3: 12---4
struct A4: 1---1
struct A5: 24---8
struct A6: 6---2
struct A6.b: 4---2
struct A7: 1---1
struct A8: 8---2
struct A9: 16---8
struct A10: 32---8
struct A11: 8---2
struct A12: 16---8
struct A13: 16---4
--------------------- end ---------------------
--------------------- test ---------------------
class Base: 1---1
class C1: 2---1
class C2: 8---4
class C3: 1---1
class Base1: 8---4
class C4: 24---8
class C41: 24---8
class C5: 24---8
class C51: 24---8
------------------------------------------------
class Base2: 8---8
class C6: 16---8
class C7: 24---8
class C8: 16---8
class Base3: 16---8
class C9: 24---8
class Base4: 8---
class C10: 16---8
class Base5: 16---
class C11: 24---8
--------------------- end ---------------------
--------------------- test ---------------------
class D1: 4---4
class D2: 1---1
--------------------- end ---------------------
- 64位編譯器
--------------------- test ---------------------
char: 1---1
int: 4---4
short: 2---2
long: 4---4
long long: 8---8
float: 4---4
double: 8---8
long double: 8---8
pointer: 8---8
--------------------- end ---------------------
--------------------- test ---------------------
struct A1: 1---1
struct A2: 8---4
struct A3: 12---4
struct A4: 1---1
struct A5: 24---8
struct A6: 6---2
struct A6.b: 4---2
struct A7: 1---1
struct A8: 8---2
struct A9: 16---8
struct A10: 32---8
struct A11: 8---2
struct A12: 16---8
struct A13: 16---4
--------------------- end ---------------------
--------------------- test ---------------------
class Base: 1---1
class C1: 2---1
class C2: 8---4
class C3: 1---1
class Base1: 8---4
class C4: 24---8
class C41: 24---8
class C5: 24---8
class C51: 24---8
------------------------------------------------
class Base2: 8---8
class C6: 16---8
class C7: 24---8
class C8: 16---8
class Base3: 16---8
class C9: 24---8
class Base4: 8---
class C10: 16---8
class Base5: 16---
class C11: 24---8
--------------------- end ---------------------
--------------------- test ---------------------
class D1: 4---4
class D2: 1---1
--------------------- end ---------------------
結果分析
分析維度
- 32位編譯和64位編譯器
- 編譯器自己的對齊命令 #pragma pack()的影響
- C關注於結構體,C++專注於類繼承和虛擬函式
- 位域的影響
- 內建結構體(或者類)物件的影響。
經驗性總結
- 對於32位編譯器和64位編譯器
除了指標大小和對齊方式不同,其他的都與32位遵循一樣的規則 #pragma pack()
再次NOTE:這裡只是預設的對齊大小,結構體不一定按這個大小進行對齊。
結論1:
假設,無#pragma pack()
情況下某結構體的對齊大小為x1;
設,存在#pragma pack(z)
指令,某結構體的對齊大小為x; 則 x = min(x1, z);
證明:
已知,無#pragma()
情況下某結構體的對齊大小為x1;預設對齊大小為z
當x1<=z, x = x1; //案例A1 –也就是說預設對齊大小,不起任何作用,平時經常遇到的就是這個情況
當z
// align(A) = max(y1, y2, y3) = 4
struct A
{
char a; //y1 = 1
int b; //y2 = 4
short c; //y3 = 2
};
對於其中,含有內嵌結構,則也類似
//易知: align(A6) = 4 align(A7) = 1
struct A6
{
char a;
int b;
short c;
};
struct A7
{
char b;
};
// align(A8) = max(y1, y2) = 4
struct A8
{
A6 c1; //y1 = 4
A7 d1; //y2 = 1
};
- 結論2:某個元素a(不管是結構體還是普通元素)的sizeof()大小一定是align(a)的整數倍。
注意很多人經常有個錯誤的言論,就是結構體的大小一定是陣列中的最長欄位的整數倍。
//注意這裡sizeof(A)=6, 不是4(= sizeof(int))的倍數,但確實是2 (=align(A))的倍數。
#pragma pack(2)
struct A
{
char a;
int b;
};
- 偏移量offset
結論3:結構體的內部元素的偏移量一定是min(其基本元素對齊大小, 預設對齊大小)的整數倍。
結構體的內部元素的偏移量是其基本元素對齊大小的整數倍。//這是錯的
因此,算偏移量的時候,都是將地址按min(其基本元素對齊大小, 預設對齊大小)大小,想象成一段一段的。(這種做法比較慢,但是適合不熟悉對齊規則的童鞋)
struct A
{
char a;
int b; //b的偏移量為4,不是1. 4是4的倍數
};
//----------------------------
#pragma pack(2)
struct A
{
char a;
int b; //b的偏移量為2
};
- 字元填充padding
為了保證結論2,有時候需要做必要的填充,
struct A
{
int a;
char b; //b的偏移量為4 4是1的倍數
};
//表面上字元的大小是5,但不是4(=align(A))的倍數,需要做字元填充,類似如下結構體
struct A
{
int a;
char b; //b的偏移量為4 4是1的倍數
char c[3];
};
- 關於位域的影響
結論4:不受結構體的真正對齊大小影響,將相應的位數按前面的型別說明符補齊。比如測試案例A11, A12
對於位域之和超過基本元素對齊大小的時候。從第一個開始超過的位域進行偏移,確保它的地址是min(其基本元素對齊大小, 預設對齊大小)大小。 比如測試案例A13
然後把它當成一個整的型別看就行。
struct A
{
short s;
int a1 : 4; //補齊int後面的28位,完全等效於一個int整體
};
- C++相關部分 (類跟結構等效)
結論5:對於一個空類,其預設sizeof()為1,align()為1;對於一個包含虛擬函式的空類,其預設sizeof()為4,align()為4
//詳見測試案例Base, Base2
結論6:對於繼承空基類的話,基類預設不佔用空間;如果是包含成員物件的話,則該物件的大小是1。
其他部分的討論,看看測試案例,看看C部分就行。
//詳見測試案例C1, C3
參考資料
寫作之外
如果有什麼好的建議,或者有什麼測試案例可以新增的歡迎留言交流。如果這篇寫作對你有所幫助,請點幫忙點個贊,讓我知道我的寫作確實幫助到別人。如果沒有幫助的話,請忽視前一句。歡迎指出不足。