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