sizeof運算符的系統總結
標籤:c/c++
總結來自 http://blog.csdn.net/w57w57w57/article/details/6626840
簡介
sizeof
是C/C++中的關鍵字,它是一個運算符,其作用是取得一個對象(數據類型或者數據對象)的長度(即佔用內存的大小,以byte爲單位)。其中類型包含基本數據類型(不包括void)、用戶自定義類型(結構體、類)、函數類型。數據對象是指用前面提到的類型定義的普通變量和指針變量(包含void指針)。不同類型的數據的大小在不同的平臺下有所區別,但是c標準規定所有編譯平臺都應該保證sizeof(char)
等於1。關於sizeof的更多概述你可以在msdn總輸入sizeof進行查詢。
看了上面這些,或許你看了沒有多少感覺。沒關係,下面我將詳細列出sizeof的諸多特性,這些特性是造成sizeof是一個較刁鑽的關鍵字的原因。特性
特性0:sizeof是運算符,不是函數
這個特性是sizeof的最基本特性,後面的很多特性都是受到這個特性的影響,正因爲sizeof不是函數,因此我們不把它括號中所要處理的對象叫做參數,可以叫做操作數(這不嚴謹,但是有助於我記住sizeof是個運算符)。
特性1:sizeof不能求得
void
類型的長度是的,你不能用
sizeof(void)
,這將導致編譯錯誤:illegalsizeof operand
。事實上你根本就無法聲明void類型的變量,不信你就試試void a;
這樣的語句,編譯器同樣會報錯:illegal use of type 'void'
。或許你要問爲什麼,很好,學東西不能只知其然,還要知其所以然。我們知道聲明變量的一個重要作用就是告訴編譯器該變量需要多少存儲空間。然而,void是“空類型”,什麼是空類型呢,可以理解成不知道存儲空間大小的類型。既然編譯器無法確定void類型的變量的存儲大小,那麼它自然不讓你聲明這樣的變量。當然了,聲明void類型的指針是可以的!這就是特性2的內容。特性2:sizeof能求得
void *
類型的長度在特性1中說過,可以聲明void類型的指針,也就是說編譯器可以確定void類型的指針所佔用的存儲空間。事實上確實如此,目前,幾乎所有平臺上的所有版本的編譯器都把指針的大小看做4byte,不信你試試
sizeof(int*); sizeof(void*); sizeof(double*); sizeof(Person*);
等等,它們都等於4!爲什麼呢?原因很簡單:其實指針也是變量,只不過這個變量很特殊,它是存放其他變量的地址的變量。又由於目前32位計算機平臺上的程序段的尋址範圍都是4GB,尋址的最小單元是byte,4GB等於232Byte,這麼多的內存其地址如果編碼呢,只需要用32個bit就行了,而32bit = 32/8 = 4byte,也就是說只需要4byte就能存儲這些內存的地址了。因此對任何類型的指針變量進行sizeof運算其結果就是4!特性3:sizeof能求得靜態分配內存的數組的長度
Int a[10]; int n = sizeof(a);
假設sizeof(int)等於4,則n= 10*4=40;特別要注意:char ch[]="abc"; sizeof(ch);
結果爲4,注意字符串數組末尾有’\0’!通常我們可以利用sizeof來計算數組中包含的元素個數,其做法是:int n = sizeof(a)/sizeof(a[0]);
【需要注意】對函數的形參數組使用sizeof的情況。舉例來說,假設有如下的函數:
void fun(int array[10]) { int n = sizeof(array); }
你覺得在fun內,n的值爲多少呢?如果你回答40的話,那麼我很遺憾的告訴你,並不是。這裏n等於4,事實上,不管形參是int的型數組,還是float型數組,或者其他任何用戶自定義類型的數組,也不管數組包含多少個元素,這裏的n都是4!爲什麼呢?原因是在函數參數傳遞時,數組被轉化成指針了,或許你要問爲什麼要轉化成指針,簡單來說:假如直接傳遞整個數組的話,那麼必然涉及到數組元素的拷貝(實參到形參的拷貝),當數組非常大時,這會導致函數執行效率極低!而只傳遞數組的地址(即指針)那麼只需要拷貝4byte。
特性4:sizeof不能求得動態分配的內存的大小!
假如有如下語句:
int *a = new int[10]; int n = sizeof(a);
那麼n的值是多少呢?是40嗎?答案是否定的!其實n等於4,因爲a是指針,在特性2中講過:在32位平臺下,所有指針的大小都是4byte!切記,這裏的a與特性3中的a並不一樣(特性3中a是數組名,此處的a是指針)!很多人都認爲數組名就是指針,其實不然,二者有很多區別的。通過特性3和特性4,我們看到了數組和指針有着千絲萬縷的關係,這些關係也是導致程序潛在錯誤的一大因素。特性3指出sizeof能求靜態分配的數組的大小,而特性4說明sizeof不能求的動態分配的內存的大小。那麼問題來了,sizeof是在什麼時候進行求值運算的?有人認爲sizeof是編譯時進行求值的,並給出理由:語句
int array[sizeof(int)*10];
能編譯通過,而很多書上都說過數組大小是編譯時就確定下來的,既然前面的語句能編譯通過,所以認爲sizeof是編譯時進行求值的。經過進一步測試我發現這個結論有些武斷!至少是有些不嚴謹!因爲在實現了c99標準的編譯器(如DEV C++)中可以定義動態數組,即語句:int num; cin>>num; int arrary[num];
是對的(注意在vc6.0中是錯的)。因此我就在DEV C++中對剛纔的array利用語句int n = sizeof(array); cout<<n<<endl
來求大小,結果編譯通過,運行時輸入num的值10之後,輸出n等於40!在這裏很明顯num的值是運行時才輸入的,因此sizeof不可能在編譯時就求得array的大小!這樣一來sizeof又變成是運行時求值的了。那麼問題來了,到底sizeof是編譯時求值還是運行時求值呢?答案就是:最開始的c標準規定sizeof只能編譯時求值,後來c99又補充規定sizeof可以運行時求值。【注意】但是,即便是在實現了c99標準的DEV C++中仍然不能用sizeof求得動態分配的內存的大小!
特性5:sizeof不能對不完整的數組求長度!
在闡述該特性之前,我們假設有兩個源文件:file1.cpp和file2.cpp
其中file1.cpp中有如下的定義:int arrayA[10] = {1,2,3,4,5,6,7,8,9,10}; int arrayB[10] = {11,12,13,14,15,16,17,18,19,20};
file2.cpp包含如下幾個語句:
extern int arrayA[]; extern int arrayB[10]; cout<<sizeof(arrayA)<<endl; //編譯出錯!! cout<<sizeof(arrayB)<<endl;
在file2.cpp中第三條語句編譯出錯,而第四條語句正確,並且能輸出40!爲什麼呢?原因就是sizeof(arrayA)試圖求不完整數組的大小。這裏的不完整的數組是指數組大小沒有確定的數組!sizeof運算符的功能就是求某種對象的大小,然而聲明:
extern int arrayA[]
只是告訴編譯器arrayA是一個整型數組,但是並沒告訴編譯器它包含多少個元素,因此對file2.cpp中的sizeof來說它無法求出arrayA的大小,所以編譯器乾脆不讓你通過編譯。那爲什麼sizeof(arrayB)又可以得到arraryB的大小呢?關鍵就在於在file2.cpp中其聲明時使用
extern int arrayB[10]
明確地告訴編譯器arrayB是一個包含10個元素的整型數組,因此大小是確定的。特性6:當表達式作爲sizeof的操作數時,它返回表達式的計算結果的類型大小,但是它不對表達式求值!
爲了說明這個問題,我們來看如下的程序語句:
char ch = 1; int num = 1; int n1 = sizeof(ch+num); int n2 = sizeof(ch = ch+num);
假設char佔用1byte,int佔用4byte,那麼執行上面的程序之後,n1,n2,ch的值是多少呢?我相信有不少人會認爲n1與n2相等,也有不少人認爲ch等於2,事實這些人都錯了。事實上n1等於4,n2等於1,ch等於1,爲什麼呢?
由於默認類型轉換的原因,表達式ch+num的計算結果的類型是int,因此n1的值爲4!而表達式
ch=ch+num;
的結果的類型是char,記住雖然在計算ch+num時,結果爲int,但是當把結果賦值給ch時又進行了類型轉換,因此表達式的最終類型還是char,所以n2等於1。n1,n2的值分別爲4和1,其原因正是因爲sizeof返回的是表達式計算結果的類型大小,而不是表達式中佔用最大內存的變量的類型大小!對於
n2=sizeof(ch=ch+num);
乍一看該程序貌似實現了讓ch加上num並賦值給ch的功能,事實並非如此!由於sizeof只關心類型大小,所以它自然不應該對表達式求值,否則有畫蛇添足之嫌了。正是因爲這點,這裏告誡各位,儘量不要在sizeof中直接對表達式求大小,以免出現錯誤,你可以將sizeof(ch = ch+num);
改寫成ch = ch +num; sizeof(ch);
雖然多了一條語句,看似冗餘了,其實好處多多:首先更加清晰明瞭,其次不會出現ch等於1這樣的錯誤(假設程序的邏輯本身就是要執行ch = ch +num;)。特性7:sizeof可以對函數調用求大小,並且求得的大小等於返回類型的大小,但是不執行函數體
假設有如下函數(是一個寫得很不好的函數,但是能很好的說明需要闡述的問題):
int fun(int& num,const int& inc) { float div = 2.0; double ret =0; num = num+inc; ret = num/div; return ret; }
那麼語句:
int a = 3; int b = 5; cout<<sizeof(fun(a,b))<<endl; cout<<a<<endl;
輸出多少呢?不同的人會給出不同的答案,我將對sizeof(fun(a,b))的值和a的值分別進行討論:
首先sizeof(fun(a,b))的值:其正確是4,因爲用sizeof求函數調用的大小時,它得到的是函數返回類型的大小,而fun(a,b)的返回類型是int,sizeof(int)等於4。很多人把函數的返回類型和返回值的類型弄混淆了,認爲sizeof(fun(a,b))的值是8,因爲函數返回值是ret,而ret被定義成double,sizeof(doube)等於8。注意,雖然函數返回值類型是double,但是在函數返回時,將該值進行了類型轉換(這裏的轉換不安全)。也有人錯誤的認爲sizeof(fun(a,b))的值是12,它們的理由是:fun內部定義了兩個局部變量,一個是float一個是double,而
sizeof(float) + sizeof(doube) = 4+8 = 12
。這樣的答案看似很合理,其實他們是錯誤地認爲這裏的sizeof是在求函數內部的變量的大小了。這當然是錯誤的。接下來看a的值:其正確答案是3!還記得特性6嗎?這裏很類似,sizeof的操作對象是函數調用時,它不執行函數體!爲此,建議大家不要把函數體放在sizeof後面的括號裏,這樣容易讓人誤以爲函數執行了,其實它根本沒執行。
既然對函數條用使用sizeof得到的是函數返回類型的大小,那麼很自然能得出這樣的結論:不能對返回類型爲void的函數使用sizeof求其大小!原因請參考特性1。同理,對返回類型是任何類型的指針的函數調用使用sizeof求得的大小都爲4,原因請參考特性2。
最後我們來看看這樣的語句:
cout<<sizeof(fun);
其答案是多少呢?其實它得不到答案,原因是編譯就通不過!最開始,我以爲能輸出答案4,因爲我認爲fun是函數名,而我知道函數名就是函數的地址,地址就是指針,於是我認爲sizeof(fun)其實就是對一個指針(函數名是指針常量?)求大小,根據特性2,任何指針的大小都是4。可是驗證時,編譯器根本不讓通過!這個是爲什麼呢?或許是標準有規定吧。暫時沒有準確的答案。特性8:sizeof求得的結構體(及其對象)的大小並不等於各個數據成員對象的大小之和!
給出相關規則,具體分析見sizeof求解結構體大小的問題(1)結構體變量中成員的偏移量必須是成員大小的整數倍(0被認爲是任何數的整數倍)
(2)結構體大小必須是所有成員大小的整數倍,也即所有成員大小的公倍數。需要提醒的是,在進行設計時,最好仔細安排結構體中各個成員的順序,因爲你已經看到了上面的結構體B與結構體A包含的成員相同,只不過順序略有差異,最終就導致了B比A多消耗了50%的空間,假如在工程中需要定義該結構體的數組,多消耗的空降將是巨大的。
特性9:sizeof不能用於求結構體的位域成員的大小,但是可以求得包含位域成員的結構體的大小!
首先解釋一下什麼是位域:類型的大小都是以字節(byte)爲基本單位的,比如sizeof(char)爲1byte,sizeof(int)爲4byte等。我們知道某個類型的大小確定了該類型所能定義的變量的範圍,比如sizeof(char)爲1byte,而1byte等於8bit,所以char類型的變量範圍是-128-127,或者0-255(unsigned char),總之它只能定義2^8=256個數!然而,要命的是bool類型只取值true和false,按理所只用1bit(即1/8byte)就夠了,但事實上sizeof(bool)等於1(使用了一個byte)。因此我們可以認爲bool變量浪費了87.5%的存儲空間!這在某些存儲空間有限的設備(比如嵌入式設備)上是不合適的,爲此需要提供一種能對變量的存儲空間精打細算的機制,這就是位域。簡單來說,在結構體的成員變量後面跟上的一個冒號+一個整數的形式,就代表位域,請看如下的結構體:
Struct A { Bool b:1; char ch1:4; char ch2:4; }item;
其中b,ch1,ch2都是位域成員。該結構體的試圖讓bool類型的變量b只佔用1個bit,讓ch1和ch2分別只佔用4個bit,以此來達到對內存精打細算的功能(事實上使用位域對內存精打細算有時候能成功,有時候卻未必)。另外需要特別注意的是:c語言規定位域只能用於int,signed int或者unsigned int類型,C++又補充了char和long類型!你不能這樣使用位域:float:9;這是不能通過編譯的。並且位域變量不能在函數或者全局區定義,只能在結構體,自定義類,聯合(union)中使用!
基於上面的結構體,語句sizeof(item.b)和sizeof(item.ch1)等對位域成員求大小的語句均不能通過編譯。其原因是:sizeof以byte爲單位返回操作數的大小!
那麼,sizeof(A)能否通過編譯呢?如何能,其結果又是多少呢?這是兩給非常好的問題,事實上我之前沒有看到任何關於這方面的論述(可能是我看的資料不足),我正是在看到sizeof(item.b)不能通過編譯時想到了這兩個問題,然後通過驗證得出了後面的結論:對包含位域的結構體是可以使用sizeof求其大小的,但其求值規則比較複雜,不僅涉及到成員對齊,還與具體編譯環境有關!在這裏你只需要知道可以對包含位域的結構體使用sizeof求其大小就可以了。