C++知識點總結(三)
19.C++記憶體分割槽
C++中的記憶體分割槽,分別是堆、棧、全域性/靜態儲存區、常量儲存區和程式碼區,如下圖所示:
每個區域的含義如下:
-
棧:儲存函式引數和區域性變數,函式執行結束時自動釋放,效率較高。
-
堆:堆是程式中預留的一塊記憶體空間,由程式設計師自己管理,需要手動申請和釋放,空間較大,但是操作不當會出現記憶體洩露和記憶體碎片。
-
全域性/靜態儲存區:分為.bss和.data兩個區域,其中.bss儲存未初始化的全域性變數和靜態變數,.data儲存已經初始化的全域性變數和靜態變數。
-
常量儲存區:儲存常量,不允許修改。
-
程式碼區:存放程式的二進位制程式碼。
20.記憶體對齊
20.1 記憶體對齊的概念
1.記憶體對齊規則
記憶體對齊:不同型別的資料在記憶體中按照一定的規則排列,而不一定是順序的一個接一個的排列。
記憶體對齊規則:
結構體中,每個變數相對於起始位置的偏移量必須是該變數型別大小的整數倍,不是整數倍空出記憶體,直到偏移量是整數倍為止。第一個成員起始於0偏移處,結構體總長度必須為裡面變數型別最大值的整數倍。
#pragma pack能夠改變編譯器的預設記憶體對齊方式,方式為:#pragma pack(n)(n為數字)
添加了#pragma pack(n)後規則就變成了下面這樣:
-
偏移量要是n和當前變數大小中較小值的整數倍。
-
整體大小要是n和最大變數大小中較小值的整數倍。
-
n值必須為1,2,4,8…,為其他值時就按照預設的分配規則
例如:
//32位系統
#pragma pack(2)
struct
{
char c1;
int i;
char c2;
}s;
int main()
{
printf("%d\n", sizeof(s)); // 不加#pragma pack(2)輸出12,加上#pragma pack(2)輸出8
return 0;
}
按照預設規則,
1)sizeof(c1) = 1 <= 4(有效對齊位),按照1位元組對齊,佔用第0單元;
2)sizeof(i) = 4 <= 4(有效對齊位),相對於結構體首地址的偏移要為4的倍數,佔用第4,5,6,7單元;
3)sizeof(c2) = 1 <= 4(有效對齊位),相對於結構體首地址的偏移要為1的倍數,佔用第8單元;
4)此時結構體s佔9個位元組,但總長度必須是4的倍數,所以結構體s佔用12個位元組;
按照#pragma pack(2)規則
1)sizeof(c1) = 1 按照1位元組對齊,佔用第0單元;
2)sizeof(i) = 4,相對於結構體首地址的偏移要為2的倍數,佔用第2,3,4,5單元;
3)sizeof(c2) = 1,相對於結構體首地址的偏移要為1的倍數,佔用第6單元;
4)此時結構體s佔7個位元組,但總長度必須是4的倍數,所以結構體s佔用8個位元組;
2.為什麼要記憶體對齊
因為CPU的讀取不是連續的,而是分成塊讀取的,塊的大小隻能是1、2、4、8、16位元組,讀取的資料未對齊,效能會大大折扣。
20.2 C++11 alignas與 alignof關鍵字
C++11 引入了aligna和alignof關鍵字,其中alignof可以計算出型別的對齊方式,alignas可以指定結構體的對齊方式(不能小於預設對齊方式)。使用示例如下:
struct s
{
char c1;
int i;
char c2;
};
struct alignas(8) s2
{
char c1;
int i;
char c2;
};
int main()
{
cout << alignof(s) << endl; //4
cout << sizeof(s) << endl; //12
cout << alignof(s2) << endl; //8
cout << sizeof(s2) << endl; //16
return 0;
}
上述示例中,s是按照預設規則對齊的,所以 alignof(s) = 4,使用 alignas 將s2按照8位元組對齊,所以alignof(s) = 8
21.記憶體洩露和記憶體溢位
記憶體洩露:記憶體洩露是指程式中已動態分配的堆記憶體沒有釋放和回收,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果,記憶體洩漏多了會導致可用記憶體空間變小,進一步造成記憶體溢位。
記憶體溢位:記憶體溢位往往是沒有足夠的記憶體空間供程式使用,原因可能如下:
1)記憶體中載入的資料過於龐大;
2)程式碼中存在死迴圈;
3)遞迴呼叫太深,導致堆疊溢位等;
4)記憶體洩漏最終導致記憶體溢位;
22.this指標
1)this指標是類的指標,指向當前物件的首地址,要用—>來訪問當前物件的所有成員變數或成員函式,所謂當前物件,是指正在使用的物件。
2)this 實際上是成員函式的一個形參,不過 this 這個形參是隱式的,它並不出現在程式碼中,而是在編譯階段由編譯器默默地將它新增到引數列表中。在呼叫成員函式時將物件的地址作為實參傳遞給 this,實際上,成員函式預設第一個引數為T * const this
例如:
class Test
{
public:
int func(int a, int b){} //在編譯器裡,func應該是:int func(Test* const this, int a, int b);
};
int main()
{
Test t;
t.func(1, 2); //編譯器會將物件的地址作為實參傳給this,即:Test::func(&t, 1, 2);
}
3)this在成員函式的開始前構造,在成員函式的結束後清除,這個生命週期同任何一個函式的引數是一樣的。
4)只有當物件被建立後 this 才有意義,所以this指標只能在成員函式內部使用,在全域性函式、靜態成員函式中都不能用this。
5)通過 this 可以訪問類的所有成員,包括 private、protected、public 屬性的。
6)this 是 const 指標,它的值是不能被修改的,一切企圖修改該指標的操作,如賦值、遞增、遞減等都是不允許的。
7)在類的非靜態成員函式中返回類物件本身的時候,直接使用 return *this;
8)如果出現成員函式的引數和成員變數重名,只能通過 this 區分。以成員函式setname(char *name)為例,它的形參是name,和成員變數name重名,如果寫作name = name;這樣的語句,就是給形參name賦值,而不是給成員變數name賦值。而寫作this -> name = name;後,=左邊的name就是成員變數,右邊的name就是形參,一目瞭然。
9)在成員函式中呼叫delete this,則類物件的空間被釋放,在delete this之後進行的其他任何函式呼叫,只要不涉及到this指標的內容,都能夠正常執行。一旦涉及到this指標,如操作資料成員,呼叫虛擬函式等,就會出現不可預期的問題。
10)在解構函式中呼叫delete this,會導致堆疊溢位。因為delete的本質是為將被釋放的記憶體呼叫一個或多個解構函式,然後,釋放記憶體。顯然,delete this會去呼叫本物件的解構函式,而解構函式中又呼叫delete this,形成無限遞迴,造成堆疊溢位,系統崩潰。
參考:
-
《C++ Primer 第5版》
-
《深入理解C++物件記憶體模型》
-
《Effective C++》