日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

理解c語(yǔ)言的sizeof

 mzsm 2015-05-23

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ì)doublelong 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>char1,short2,int4long8,double8,long double16。

一個(gè)基本原則就是結(jié)構(gòu)體里的每個(gè)成員都能按照自己類型的align_size去對(duì)齊。計(jì)算過(guò)程如下:


很顯然,編譯器在計(jì)算過(guò)程中會(huì)自動(dòng)填充空余部分。否則,想想一個(gè)結(jié)構(gòu)體,內(nèi)部空間都不是連續(xù)填充的,會(huì)讓編譯器分配空間時(shí)瘋掉的。

代碼示例

我們舉例看看,long double的類型大小是16字節(jié)。

struct S{

char a;

long double b[1];

};

struct S sample;

printf('%ld,%ld\n', sizeof(struct S), sizeof(sample.b));

打印結(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ù)呢?改為:

struct S *sample =(struct S*)malloc(sizeof(long double) * 1234 +sizeof(struct S));

結(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}}這種方式:

#include <stdio.h>

struct S{

char a;

long double b[];

};

struct S sample={'1',{1,2,3}};

main ()

{

printf('%Lf, %Lf, %Lf\n', sample.b[0], sample.b[1], sample.b[2]);

//printf('%ld,%ld\n', sizeof(sample),sizeof(sample.b));//加上此句會(huì)編譯不過(guò)

return 0;

}

初始化賦值是正常的,但是sizeof那句編譯不過(guò):

error: invalid application of ‘sizeof’ to incomplete type ‘long double[]’

另外,對(duì)于b[]這種非零數(shù)組成員的結(jié)構(gòu)體必須為全局靜態(tài)定義,否則編譯不過(guò):

error: non-static initialization of a flexible array member

必須全局靜態(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ǔ)空間了。

#include <stdio.h>

main ()

{

struct S{

char a;

long double b[0];

};

struct S sample={'1',{1,2,3}};

printf('%Lf, %Lf, %Lf\n', sample.b[0], sample.b[1], sample.b[2]);

printf('%ld,%ld\n', sizeof(sample),sizeof(sample.b));

return 0;

}

b[0]表面上可以正常初始化賦值,但是實(shí)際沒(méi)有做處理的:編譯期間可以看到gcc的數(shù)組越界初始化警告:(此時(shí)會(huì)體會(huì)到-Werror的好處了)

warning: excess elements in array initializer [enabled by default]

warning: (near initialization for ‘sample.b’) [enabled by default]

帶著警告的運(yùn)行實(shí)現(xiàn)結(jié)果:

0.000000, -0.000000, 0.000000

16,0

顯然,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)空間)分配空間的。

#include<stdio.h>

intmain(void)

{

int i;

scanf('%d', &i);

char str[i];

printf('sizeof(str[%d])=%d\n',i,sizeof(str));

return 0;

}

如果輸入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è):

#include<limits.h>

#include<stdio.h>

intmain(int argc, char *argv[])

{

int i, n;

n = atoi(argv[1]);

char str[n+1];

for (i = 0; i < n; i++) {

str[i] = (char)('0' + i);

}

str[n]='\0';

printf('str is %s\n', str);

printf('str:%ld, %ld\n',sizeof(str[ULONG_MAX]),sizeof(str));

return 0;

}

當(dāng)入?yún)rgv[1]為“3”時(shí),看結(jié)果:

str is 012

str:1, 4

可見(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:

#include <stdio.h>

int main( void )

{

int i;

for(i=0;i<2;i++) {

int m,n;

scanf('%d %d', &m,&n);

char a[m][n];

char (*p)[n]=a;

printf('%ld %ld', sizeof(a), sizeof(*p));

}

return 0;

}

輸入“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ù)組的效果:

#include <stdio.h>

void func(int,int,long double a[*][*]);

void func2(long double a[2][6]);

int main(void)

{

int m=2, n=3;

long double a[m][m*n];

func(m,n,a);

long double b[2][6];

func2(b);

return 0;

}

void func(int m,int n,long double x[m][m*n])

{

printf('%ld %ld %ld\n',sizeof(x), sizeof(x[0]), sizeof(x[0][0]));

}

void func2(long double x[2][6]){

printf('%ld %ld %ld\n',sizeof(x), sizeof(x[0]), sizeof(x[0][0]));

}

輸出結(jié)果表明,兩種處理是一樣的:

8 96 16

8 96 16

可見(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è)例子,就明白了:

#include <stdio.h>

void func(long double x[][3]){

printf('%ld %ld %ld\n',sizeof(x), sizeof(x[0]), sizeof(x[0][0]));

}

int main(void)

{

long double a[1][3];

long double b[2][3];

func(a);

func(b);

return 0;

}

沒(méi)錯(cuò),是gcc編譯器支持的這種數(shù)組第一維可以省略的參數(shù)形式的后遺癥。

總結(jié)



    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多