c語言有很多用起來需要特別注意的地方,我們(計算機學習微信公眾號:jsj_xx)以后會分析其中有使用價值的點。今天我們一起看看sizeof。c語言通過類型長度來達到指針的靈活性,我們覺得,某種意義上講,是sizeof功能成就了c指針。 基礎知識 首先,要知道sizeof 是關鍵字不是函數(shù)。也就是說,用到sizeof的地方其實在編譯階段就已經(jīng)計算出結果了,不是(也不能)在程序運行時動態(tài)地計算。換句話說,代碼中同一個sizeof的調用只能輸出一個值,而不可能有其它別的值(后文會看到,其實變長數(shù)組是顛覆了這個規(guī)律的)。再換句話說,就是反匯編就能看到sizeof調用的結果! 其次,sizeof的計算結果跟編譯器的字節(jié)對齊方式有關。在默認情況下,c編譯器為每一個變量按其類型大小分配空間,這種默認方式是可以修改的,通過#pragma pack (n)或者__attribute((aligned (n)))。 最后,要知道對齊是個性能要求,不是必須的。我們這里僅考慮gcc編譯器,不考慮vc編譯器。比如ia32下,gcc對double、long long這樣的8字節(jié)變量,仍然是按4字節(jié)對齊,即使設置#pragma pack(8)的情況下。到了x64_64下,開始統(tǒng)一了,全部真的都是按照類型大小對齊的了。至于為何跟性能相關,我們以后講到cpu cache時再重新考慮這個問題,現(xiàn)在我們只要知道,因為地址總線放地址時肯定都是對齊的,所以不對齊的話會增加讀取周期就行了。 下文全部以gcc+x86_64+結構體,來求解sizeof。 計算原理 對于每個數(shù)據(jù)類型都有一個align_size,其實就是類型大?。?/span>char是1,short是2,int是4,long是8,double是8,long double是16。 一個基本原則就是結構體里的每個成員都能按照自己類型的align_size去對齊。計算過程如下:
代碼示例 我們舉例看看,long double的類型大小是16字節(jié)。
打印結果是“32,16”。因為結構體成員中最大的align_size(數(shù)組的話,看數(shù)組里面的單個元素的align_size)是16字節(jié),所以整個結構體就是16字節(jié)對齊的,這樣char也自動填充到16字節(jié)了。故,總的結構體大小就是32字節(jié)了。 那么,將結構體的b[1]改為b[0]呢?也就是說,結構體內部引入零數(shù)組成員。 此時結果是“16,0”。也就是說該結構體還是16字節(jié)對齊,說明零數(shù)組元素還是參與了計算結構體的align_size,但是沒有占用空間。 那么,還是維持為b[0],我們利用零數(shù)組的特性來放點數(shù)據(jù)呢?改為:
結果還是“16,0”。也就是說跟零數(shù)組的數(shù)據(jù)大小沒有關系,sizeof是感知不到的。 那么,如果不用malloc,直接初始化賦值呢?因為我們知道字符串數(shù)組是可以這么做的,但是作為結構體成員呢?改為struct s sample={'1',{1,2,3}}這種方式:
初始化賦值是正常的,但是sizeof那句編譯不過:
另外,對于b[]這種非零數(shù)組成員的結構體必須為全局靜態(tài)定義,否則編譯不過:
必須全局靜態(tài)存儲區(qū)間存放,才允許初始化賦值,是可以理解的,因為字符串數(shù)組(即使是定義在函數(shù)內部也能直接賦值。區(qū)別是char *str='123'是存放在全局靜態(tài)空間,char str[]='123'是存放在棧空間,并且后者的sizeof是能得出數(shù)組大小的)就是這么實現(xiàn)的。但是在加上sizeof(sample.b)就編譯不過,難道是gcc沒有做好?因為編譯期間是可以通過計算全局靜態(tài)區(qū)間的b[]大小算出來的。(為方便下文描述,這種形式我們稱為非零長數(shù)組) 那么,再看看改為b[0]會怎么樣。此時是零長數(shù)組了,所以不必放在全局靜態(tài)存儲空間了。
b[0]表面上可以正常初始化賦值,但是實際沒有做處理的:編譯期間可以看到gcc的數(shù)組越界初始化警告:(此時會體會到-Werror的好處了)
帶著警告的運行實現(xiàn)結果:
顯然,b[]中3個數(shù)值沒有正確處理。而sizeof的運算結果和上面的一致:還是無法感知數(shù)組大小。 我們最后拋開結構體內成員限制(下文有原因),觀察下變長數(shù)組:元素個數(shù)是一個變量(跟上面的b[]形式的非零長數(shù)組是有區(qū)別的)。它實際就是在棧空間(既然變長,肯定不能是全局靜態(tài)空間)分配空間的。
如果輸入i為3,結果為“3,3”。對sizeof而言,變長數(shù)組是維持了數(shù)組的sizeof特性,畢竟以前普通數(shù)組就是這樣的效果!需要注意的是,變長數(shù)組只能定義在棧空間,不能全局靜態(tài)存儲空間或堆空間定義,而且由編譯器會盡量放到棧幀局部變量部分的最后。放在最后,是為了方便擴展,那如果塊范圍內有多個變長數(shù)組呢?多個就一個一個放到最后,逐個順序擴展(后面會有示例)。 那如果變長數(shù)組作為入?yún)⒛??也是維持了數(shù)組的sizeof特性,還是和以前一樣:對入?yún)⒆鰏izeof,結果就是指針長度8。(此處僅考慮一維的情況,后文會考慮多維) 涉及多維數(shù)組以及指針的內容,我們(計算機學習微信公眾號:jsj_xx)以后再講。。。 關于變長數(shù)組的補充 變長數(shù)組(variable length array,即VLA)是c99引入的,我們上面看過一個例子了?,F(xiàn)在再看一個:
當入?yún)rgv[1]為“3”時,看結果:
可見,對于變長數(shù)組,感知到了數(shù)組大??!另外,我們看到sizeof(str[ULONG_MAX])也是可以的,看來計算時是只看元素類型大小,不考慮數(shù)組下標范圍的! 總結變長數(shù)組的特點,如下: 1)必須在塊范圍內定義,不能在文件范圍內定義(static修飾)或全局引用(extern修飾),即保證只能是??臻g分配; 2)變長數(shù)組不能作結構體或者聯(lián)合的成員,只能以獨立數(shù)組方式存在; 3)作用域為塊范圍,即其生存周期為所在塊入棧和出棧之間的時間內; 第二個特點也是我們不去設置變長數(shù)組成員的結構體的原因。 其實有個函數(shù)功能和變長數(shù)組類似,就是void alloca(size_t size)。它也是在棧中分配size字節(jié)大小的空間,當本棧幀退出時釋放空間。要注意的是,alloca()執(zhí)行失敗時,不會返回一個NULL指針,因為它本質就是一條調整棧頂指針的匯編指令,不能有豐富的返回值。匯編實現(xiàn)就導致了很差的移植性,所以,相對而言,這種場景更應該用變長數(shù)組。 最后看下,變長數(shù)組在多維時的處理??创a:
輸入“3,4”時,結果是“12,4”;輸入“5,6”時,結果是“30,6”。 可見,變長數(shù)組在各個維度上都很好地維持了普通數(shù)組的sizeof特性。另外,通過for循環(huán)也看到了塊范圍內變長數(shù)組可使用的重復性。 對于二維變長數(shù)組作為函數(shù)參數(shù)也維持了普通二維數(shù)組的效果:
輸出結果表明,兩種處理是一樣的:
可見,變長多維數(shù)組維持普通多維數(shù)組一樣的效果:直接對入?yún)⑷izeof的結果是指針長度8,其它都能正常感知數(shù)組大小。為何數(shù)組做入?yún)r,sizeof就識別不出來,只能做指針大小處理呢?看懂下面這個例子,就明白了:
沒錯,是gcc編譯器支持的這種數(shù)組第一維可以省略的參數(shù)形式的后遺癥。 總結
|
|