1. 程式人生 > 其它 >C++知識點總結(三)

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,形成無限遞迴,造成堆疊溢位,系統崩潰。

參考:

  1. 《C++ Primer 第5版》

  2. 《深入理解C++物件記憶體模型》

  3. 《Effective C++》

  4. http://c.biancheng.net/cplus/