詳解C++ sizeof(下)
sizeof作用于基本數(shù)據(jù)類型,在特定的平臺和特定的編譯器中,結果是確定的,如果使用sizeof計算構造類型:結構體、聯(lián)合體和類的大小時,情況稍微復雜一些。
1.sizeof計算結構體
考察如下代碼:
struct S1 { char c; int i; }; cout<<”sizeof(S1)=”<<sizeof(S1)<<endl;
sizeof(S1)結果是8,并不是想象中的sizeof(char)+sizeof(int)=5。這是因為結構體或類成員變量具有不同類型時,需進行成員變量的對齊。《計算機組成原理》一書中說明,對齊的目的是減少訪存指令周期,提高CPU存儲速度。
1.1內(nèi)存對齊原則
(1)結構體變量的首地址能夠被其最寬基本成員類型大小所整除;
(2)結構體每個成員相對于結構體首地址的偏移量都是成員大小的整數(shù)倍,如有需要編譯器會在成員之間加上填充字節(jié);
(3)結構體的總大小為結構體最寬基本成員類型大小的整數(shù)倍,如有需要編譯器會在最末一個成員之后加上填充字節(jié)。
有了以上三個內(nèi)存對齊的原則,就可以輕松應對嵌套結構體類型的內(nèi)存對齊。如下:
struct S2 { char c1; S1 s; char c2; };
在尋找S2的最寬基本數(shù)據(jù)類型時,包括其嵌套的結構體中的成員,從S1中尋找出最寬結構體數(shù)據(jù)類型是int,因此S2的最寬數(shù)據(jù)類型是int。S1 s在結構體S2中的對齊也遵守前三個準則,因此sizeof(S2)=sizeof(char)+pad(3)+sizeof(S1)+1+pad(3)=1+3+8+1+3=16字節(jié),其中pad(3)表示填充3個字節(jié)。
結構體某個成員相對于結構體首地址的偏移量可以通過宏offsetof()來獲得,這個宏也在stddef.h中定義,如下:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
例如獲得S1中的偏移量,方法為
size_t pos = offsetof(S1, i); //pos等于4
1.2修改對齊方式
1.2.1#pragma pack
#pragma pack(n)
中n為字節(jié)對齊數(shù),其取值為1、2、4、8、16,默認是8。結構體對齊時,
(1)成員的偏移量為成員本身大小和n二者最小值的整數(shù)倍;
(2)結構體最終大小是結構體中最寬基本類型成員大小和n二者中的最小值的整數(shù)倍。
考察如下代碼:
#pragma pack(push) //將當前pack設置壓棧保存 #pragma pack(2) //必須在結構體定義之前使用 struct S1 { char c; int i; }; struct S2 { char c1; S1 s; char c2 }; #pragma pack(pop) // 恢復先前的pack設置 //或者 #pragma pack(2) ... #pragma pack()
因此,sizeof(S2)=sizeof(char)+pad(1)+sizeof(S1)+1+pad(1)=1+1+6+1=10字節(jié)。
注意,#pragma pack不能指定變量的存儲地址,變量的首地址默認為最大基本成員類型大小的整數(shù)倍。
1.2.2__declspec(align(#))
VC++支持__declspec(align(#)),在GNU C++并不支持。#的取值為1~8192,為2的冪。使用示例如下:
__declspec(align(256)) struct TestSize { char a; int i; }; cout<<sizeof(TestSize)<<endl; //輸出256
__declspec(align(#))要求#為2的整數(shù)次冪,作用主要有兩個方面:
(1)使結構體或類成員按#pragma pack確定內(nèi)存布局之后,在末尾填充內(nèi)存使得整個對象的大小至少是#的整數(shù)倍。
(2)作用于變量時,強制要求編譯器將變量放置在地址是#整數(shù)倍的內(nèi)存位置上。這點在調(diào)用原生API等要求嚴格對齊的方法時十分重要。
1.3空結構體
C/C++中不允許長度為0的數(shù)據(jù)類型存在。對于“空結構體”(不含數(shù)據(jù)成員)的大小不為0,而是1。“空結構體”變量也得被存儲,這樣編譯器也就只能為其分配一個字節(jié)的空間用于占位了。如下:
struct S3 { }; sizeof(S3); // 結果為1
1.4位域結構體
有些信息在存儲時,并不需要占用一個完整的字節(jié), 而只需占一個或多個二進制位。例如在存放一個開關量時,只有0和1 兩種狀態(tài), 用一位即可表示。為了節(jié)省存儲空間,并使處理簡便,C語言又提供了一種數(shù)據(jù)結構,稱為”位域”或”位段”。包含位域變量的結構體叫作位域結構體。位域結構體的定義形式:
struct 位域結構體名 { 類型說明符 位域名:位域長度; ... };
注意,位域長度不應該大于該類型說明符對應的數(shù)據(jù)類型的位長度。
使用位域的主要目的是壓縮存儲,其大致規(guī)則為:
(1)如果相鄰位域字段的類型相同,且其位寬之和小于類型的sizeof大小,則后面的字段將緊鄰前一個字段存儲,直到不能容納為止;
(2)如果相鄰位域字段的類型相同,但其位寬之和大于類型的sizeof大小,則后面的字段將從新的存儲單元開始,其偏移量為其類型大小的整數(shù)倍;
(3)如果相鄰位域字段的類型不同,則各編譯器的具體實現(xiàn)有差異,VC++采取不壓縮方式,GNU C++采取壓縮方式;
(4)如果位域字段之間穿插著非位域字段,則不進行壓縮;
(5)整個結構體的總大小為最寬基本類型成員大小的整數(shù)倍;
(6)位域可以無位域名,這時它只用作填充或調(diào)整位置,不能使用。例如:
struct BitFiledStruct { int a:1; int :2; //該2位不能使用 int b:3; int c:2; };
關于位域結構體的sizeof大小,考察如下代碼:
#include <iostream> using namespace std; struct BFS1 { char f1 : 3; char f2 : 4; char f3 : 5; }; struct BFS2 { char f1 : 3; int i : 4; char f2 : 5; }; struct BFS3 { char f1 : 3; char f2; char f3 : 5; }; int main() { cout<<sizeof(BFS1)<<endl; cout<<sizeof(BFS2)<<endl; cout<<sizeof(BFS3)<<endl; }
運行上面的程序,VC++和GNU C++輸出結果如下:
//VC++輸出結果
2
12
3//GNU C++輸出結果
2
4
3
考察以上代碼,得出:
(1)sizeof(BFS1)==2。當相鄰位域類型不同,在VC++中sizeof(BFS2)=1+pad(3)+4+1+pad(3)=12,采用不壓縮方式,位域變量i的偏移量需要是4的倍數(shù),并且位域結構體BFS2的總大小必須是sizeof(int)的整數(shù)倍。在GNU C++中為sizeof(BFS2)=4,相鄰的位域字段的類型不同時,采取了壓縮存儲,位域變量i緊隨位域變量f1的剩余位進行存儲,位域變量f2同樣是緊隨位域變量i的剩余位進行存儲,并且位域結構體BFS2的總大小必須是sizeof(int)的整數(shù)倍,所以最終結果sizeof(BFS2)=1+pad(3)=4。
(2)sizeof(BFS3)==3,當非位域字段穿插在其中,不會產(chǎn)生壓縮,在VC++和GNU C++中得到的大小均為3,如果壓縮存儲,則sizeof(BFS3)==2。
2.sizeof計算共用體
結構體在內(nèi)存組織上是順序式的,共用體則是重疊式,各成員共享一段內(nèi)存,所以整個共用體的sizeof也就是每個成員sizeof的最大值。結構體的成員也可以是構造類型,這里,構造類型成員是被作為整體考慮的。所以,下面例子中,假設sizeof(s)的值大于sizeof(i)和sizeof(c),那么sizeof(U)等于sizeof(s)。
union U { int i; char c; S1 s; };
3.sizeof計算類
類是C++中常用的自定義構造類型,有數(shù)據(jù)成員和成員函數(shù)組成,進行sizeof計算時,和結構體并沒有太大的區(qū)別??疾烊缦麓a:
#include <iostream> using namespace std; class Small{}; class LessFunc { int num; void func1(){}; }; class MoreFunc { int num; void func1(){}; int func2(){return 1;}; }; class NeedAlign { char c; double d; int i; }; class Virtual { int num; virtual void func(){}; }; int main(int argc,char* argv[]) { cout<<sizeof(Small)<<endl; //輸出1 cout<<sizeof(LessFunc)<<endl;//輸出4 cout<<sizeof(MoreFunc)<<endl;//輸出4 cout<<sizeof(NeedAlign)<<endl;//輸出24 cout<<sizeof(Virtual)<<endl; //輸出8 return 0; }
注意一點,C++中類同結構體沒有本質的區(qū)別,結構體同樣可以包含成員函數(shù),構造函數(shù),析構函數(shù),虛函數(shù)和繼承,但一般不這么使用,沿用了C的結構體使用習慣。類與結構體唯一的區(qū)別就是結構體的成員的默認權限是public,而類是private。
基于以上這點,再考察從程序的輸出結果,得出如下結論:
(1)類同結構體一樣,C++中不允許長度為0的數(shù)據(jù)類型存在,雖然類無任何成員,但該類的對象仍然占用1個字節(jié)。
(2)類的成員函數(shù)并不影響類對象占用的空間,類對象的大小是由它數(shù)據(jù)成員決定的。
(3)類和結構體一樣,同樣需要對齊,具體對齊的規(guī)則見上文結構體的內(nèi)存對齊。
(4)類如果包含虛函數(shù),編譯器會在類對象中插入一個指向虛函數(shù)表的指針,以幫助實現(xiàn)虛函數(shù)的動態(tài)調(diào)用。
所以,該類的對象的大小至少比不包含虛函數(shù)時多4個字節(jié)。如果考慮內(nèi)存對齊,可能還要多些。如果使用數(shù)據(jù)成員之間的對齊,當類對象至少包含一個數(shù)據(jù)成員,且擁有虛函數(shù),那么該對象的大小至少是8B,讀者可自行推導。
以上就是詳解C++ sizeof(下)的詳細內(nèi)容,更多關于C++ sizeof的資料請關注腳本之家其它相關文章!
相關文章
Visual C++程序設計中Windows GDI貼圖閃爍的解決方法
這篇文章主要介紹了Visual C++程序設計中Windows GDI貼圖閃爍的解決方法,分析了GDI貼圖閃爍的常見原因及其具體解決方法,具有一定參考借鑒價值,需要的朋友可以參考下2015-01-01C語言 超詳細介紹與實現(xiàn)線性表中的帶頭雙向循環(huán)鏈表
帶頭雙向循環(huán)鏈表:結構最復雜,一般用在單獨存儲數(shù)據(jù)。實際中使用的鏈表數(shù)據(jù)結構,都是帶頭雙向循環(huán)鏈表。另外這個結構雖然結構復雜,但是使用代碼實現(xiàn)以后會發(fā)現(xiàn)結構會帶來很多優(yōu)勢,實現(xiàn)反而簡單2022-03-03