1.空類的大小是1.
class Empty{};int main(){cout<<sizeof(Empty)<<endl;}
一個空類的大小是1,而不是0.這是為什麼呢,因為如果你定義了這個類的對象,那麼你得有它的記憶體位址才行,而如果它的大小為0,它就不佔記憶體位址了,所以,編譯器將它的大小設為了1.
2.靜態成員不計入類的大小中
class Empty{public:static int val;};int Empty::val = 1;int main(){cout<<sizeof(Empty)<<endl;}
大小還是1,static變數是在程式執行之前分配的在靜態儲存區的,它的大小並不算在類的大小之內。
3.基類的私人成員也被衍生類別繼承下來了,但是不可被訪問,並不是衍生類別中沒有這個成員。
class C1{private:int v1;};class C2:public C1{private:int v2;};
它們的大小一個是4,一個是8.也許有人會覺得,C1的v1是private的,不會被繼承下來吧?其實是,它會被繼承下來,但是不可被訪問。
4.虛函數會增加類的大小。
class C1{virtual void func(){}};class C2:public C1{};
這兩個類大小均為4.這是因為:虛函數的實現,依賴於虛函數表,這兩個類都儲存了虛函數表的地址,這是一個指標,需要4個位元組。
6.下面我們考慮一個筆試中經常考的問題,結構體(類)的大小。
首先,咱們不考慮微軟提供的修改對其方式的代碼:#pragma pack和__declspec,考慮C++本身的情況:
class C1{char str;int ival;double dval;};class C2{int ival;double dval;char str;};
它們的大小分別為16和24。為什麼呢?因為在定義它們的大小時,需要考慮對齊的因素:
char類型的大小為1,所以它的地址是可以從任何位移處開始的;int的大小為4,所以它的起始地址一定是4的倍數;同理,double類型的起始地址一定是8的倍數。而整個類的大小,是這個類成員中最大的size的整數倍。
拿我們的例子來說,對於C1:str佔了1個位元組,然後空了3個位元組放ival,然後接著放dval。大小為1+3+4+8 = 16;對於C2:ival佔了4個位元組,接下來空4個位元組,放dval,然後放str,然後補上7個位元組(滿足8的整數倍),所以總的大小為:4+4+8+1+7 = 24;
所以,假設你的類或者結構體有很多變數的話,最好是把它們從大到小的擺放會比較節省空間的。
在上面的描述中,有一點是不確切的:而整個類的大小,是這個類成員中最大的size的整數倍。這是對於基本類型而言的,假設我們為其增加了數組:
class C1{char str;int ival;double dval;int arr[10];};class C2{int arr[10];int ival;double dval;char str;};
那麼它倆的大小就變為56、64,並不是最大成員arr的size(40)的整數倍,而是8的整數倍,可見,當數組的大小參與對齊計算時,是按照每個元素的大小來衡量的,舉個例子:
class C4{int val;char str;double arr[10];};
它的大小為88:val佔了4個,str佔了1個,然後空了3個,到達了位移量為8的地址,開始放arr,它的大小為80,一共為88。想想道理吧,對齊的目的只是為了更加快捷的訪問,所以,而數組是連續的,每個元素之間的位移量是固定的,所以完全沒有必要把整個大小弄成數組的大小的整數倍。
類似的如果一個類裡麵包含了其他成員類,那麼在計算整個類的大小時,對待子類時只使用它的成員中最大的元素的來參與對齊;而不是這個類的總大小:
class C4{double val;char str;int arr[10];};class C1{char str;int ival;double dval;C4 obj;};class C2{char str;C4 obj;int ival;double dval;};
在C1和C2中,包含了一個類成員C4,我們先看它的大小:val佔了8個,str佔了1個,然後空上3個,然後是40個位元組的arr,然後整個類的大小,是其中最大成員val的大小(8)的整數倍,所以還得補充4個:8+1+3+40 +4= 56;
下面再分析C1:char佔了一個,因為下面的是int,所以空3個,放ival,然後接著放dval,然後再放obj,總的大小為1+3+4+8+56 = 72;
最後看C2:char佔了1個,因為後面的是C4類型,要以8位元組對齊,所以補7個,然後放obj,然後放val,此時的大小為:1+7+56+4 = 68,因為下面是一個duoble,所以要以8對齊,所以要在填4個,到了72,然後放dval,總的大小為:1+7+56+4+4+8 = 80。
基本的情況就是這樣,但是微軟為我們提供了特殊的指令來修改對其方式:#pragma pack(n)和__declspec(align(n))。
7.
先看#pragma pack,它指明了對齊的位元組數:每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數中較小的一個對齊。
比如#pragma pack(16)不會對我們的類的大小產生任何影響,而#pragma pack(2)意味著類的成員要以2個位元組對齊,所以C4大大小為:8(val)+1(str)+1(空)+40 = 50;而C1的大小為:1(str)+1(空)+4(ival)+8(dval)+50 = 64,同理C2的大小也是64。如果你使用#pragma pack(1),則類裡面的元素就是緊緊挨在一起的,沒有C4的大小為49,而C1、C2均為62。
這個預先處理命令還有一些其他的用法,但與主題關係不太密切,就不多說了。
8.
declspec( align() )的一個特點是,它僅僅規定了資料對齊的位置,而沒有規定資料實際佔用的記憶體長度,當指定的資料被放置在確定的位置之後,其後的資料填充仍然是按照#pragma pack規定的方式填充的,這時候類/結構的實際大小和記憶體格局的規則是這樣的:
在__declspec( align() )之前,資料按照#pragma pack規定的方式填充,如前所述。當遇到__declspec( align() )的時候,首先尋找距離當前位移向後最近的對齊點(滿足對齊長度為max(資料自身長度,指定值) ),然後把被指定的資料類型從這個點開始填充,其後的資料類型從它的後面開始,仍然按照#pragma pack填充,直到遇到下一個__declspec( align() )。
當所有資料填充完畢,把結構的整體對齊數值和__declspec( align() )規定的值做比較,取其中較大的作為整個結構的對齊長度。
特別的,當__declspec( align() )指定的數值比對應類型長度小的時候,這個指定不起作用。舉個例子:
#pragma pack(2)__declspec(align(16))class C4{public:double val;//起始位移地址為0,佔8個位元組char str;//起始位移地址為8,佔一個位元組,然後填充一個位元組int arr[10];//起始地址為10,佔40個位元組};//資料填充完畢,大小為50,與50最近的16的倍數是64,所以總的大小為64。
最後,如果想查看某個變數的位移地址,可以通過offsetof來查看。具體內容可自行百度。