日本a√视频在线,久久青青亚洲国产,亚洲一区欧美二区,免费g片在线观看网站

        <style id="k3y6c"><u id="k3y6c"></u></style>
        <s id="k3y6c"></s>
        <mark id="k3y6c"></mark>
          
          

          <mark id="k3y6c"></mark>

          新聞中心

          EEPW首頁(yè) > 嵌入式系統(tǒng) > 牛人業(yè)話 > C語(yǔ)言的那些小秘密之字節(jié)對(duì)齊

          C語(yǔ)言的那些小秘密之字節(jié)對(duì)齊

          作者: 時(shí)間:2015-04-18 來(lái)源:網(wǎng)絡(luò) 收藏

            可能有不少讀者會(huì)問(wèn),對(duì)齊有必要拿出來(lái)單獨(dú)寫(xiě)一篇博客嘛?我覺(jué)得是很有必要,但是它卻是被很多人所忽視的一個(gè)重點(diǎn)。那么我們使用對(duì)齊的作用和原因是什么呢?由于硬件平臺(tái)之間對(duì)存儲(chǔ)空間的處理上是有很大不同的,一些平臺(tái)對(duì)某些特定類型的數(shù)據(jù)只能從某些特定地址開(kāi)始存取,如通常有些架構(gòu)的CPU要求在編程時(shí)必須保證對(duì)齊,否則訪問(wèn)一個(gè)沒(méi)有進(jìn)行字節(jié)對(duì)齊的變量的時(shí)候會(huì)發(fā)生錯(cuò)誤。而有些平臺(tái)可能沒(méi)有這種情況,但是通常的情況是如果我們編程的時(shí)候不按照適合其平臺(tái)要求對(duì)數(shù)據(jù)存放進(jìn)行對(duì)齊,會(huì)在存取效率上帶來(lái)?yè)p失。比如有些平臺(tái)每次讀都是從偶地址開(kāi)始,如我們操作一個(gè)int型數(shù)據(jù),如果存放在偶地址開(kāi)始的地方,那么一個(gè)讀周期就可以讀出,而如果存放在奇地址開(kāi)始的地方,就可能會(huì)需要2個(gè)讀周期,兩個(gè)周期讀取出來(lái)的字節(jié)我們還要對(duì)它們進(jìn)行高低字節(jié)的拼湊才能得到該int型數(shù)據(jù),從而使得我們的讀取效率較低,這也從側(cè)面反映出了一個(gè)問(wèn)題,就是我們很多時(shí)候是在犧牲空間來(lái)節(jié)省時(shí)間的。

          本文引用地址:http://yuyingmama.com.cn/article/272734.htm

            可能看了上面的講解你還是不太明白,那我們?cè)賮?lái)看一次什么是字節(jié)對(duì)齊呢? 我們現(xiàn)在的計(jì)算機(jī)中內(nèi)存空間都是按照字節(jié)來(lái)進(jìn)行劃分的,從理論上來(lái)講的話似乎對(duì)任何類型的變量的訪問(wèn)可以從任何地址開(kāi)始,然而值得注意的就是,實(shí)際情況下在訪問(wèn)特定變量的時(shí)候經(jīng)常在特定的內(nèi)存地址訪問(wèn),從而就需要各種類型的數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對(duì)齊。

            按照預(yù)先的計(jì)劃安排,這次應(yīng)該是寫(xiě)《的那些小秘密之鏈表(三)》的,但是我發(fā)現(xiàn)如果直接開(kāi)始講解linux內(nèi)核鏈表的話,可能有些地方如果我們不在此做一個(gè)適當(dāng)?shù)闹v解的話,有的讀者看起來(lái)可能難以理解,所以就把字節(jié)對(duì)齊挑出來(lái)另寫(xiě)一篇博客,我在此盡可能的講解完關(guān)于字節(jié)對(duì)齊的內(nèi)容,希望我的講解對(duì)你有所幫助。

            在此之前我們不得不提的一個(gè)操作符就是sizeof,其作用就是返回一個(gè)對(duì)象或者類型所占的內(nèi)存字節(jié)數(shù)。我們?yōu)槭裁床辉诖朔Q之為sizeof()函數(shù)呢?看看下面一段代碼:

            [html] view plaincopy#include

            void print()

            {

            printf("hello world!n");

            return ;

            }

            void main()

            {

            printf("%dn",sizeof(print()));

            return ;

            }

            這段代碼在linux環(huán)境下我采用gcc編譯是沒(méi)有任何問(wèn)題的,對(duì)于void類型,其長(zhǎng)度為1,但是如果我們?cè)趘c6下面運(yùn)行的話話就會(huì)出現(xiàn)illegal sizeof operand錯(cuò)誤,所以我們稱之為操作符更加的準(zhǔn)確些,既然是操作符,那么我們來(lái)看看它的幾種使用方式:

            1、sizeof( object ); // sizeof( 對(duì)象 );

            2、 sizeof( type_name ); // sizeof( 類型 );

            3、sizeof object; // sizeof 對(duì)象; 通常這種寫(xiě)法我們?cè)诖a中都不會(huì)使用,所以很少見(jiàn)到。

            下面來(lái)看段代碼加深下印象:

            [html] view plaincopy#include

            void main()

            {

            int i;

            printf("sizeof(i):t%dn",sizeof(i));

            printf("sizeof(4):t%dn",sizeof(4));

            printf("sizeof(4+2.5):t%dn",sizeof(4+2.5));

            printf("sizeof(int):t%dn",sizeof(int));

            printf("sizeof 5:t%dn",sizeof 5);

            return ;

            }

            運(yùn)行結(jié)果為:



            [html] view plaincopysizeof(i): 4

            sizeof(4): 4

            sizeof(4+2.5): 8

            sizeof(int): 4

            sizeof 5: 4

            Press any key to continue

            從運(yùn)行結(jié)果我們可以看出上面的幾種使用方式,實(shí)際上,sizeof計(jì)算對(duì)象的大小也是轉(zhuǎn)換成對(duì)對(duì)象類型的計(jì)算,也就是說(shuō),同種類型的不同對(duì)象其sizeof值都是一樣的。從給出的代碼中我們也可以看出sizeof可以對(duì)一個(gè)表達(dá)式求值,編譯器根據(jù)表達(dá)式的最終結(jié)果類型來(lái)確定大小,但是一般不會(huì)對(duì)表達(dá)式進(jìn)行計(jì)算或者當(dāng)表達(dá)式為函數(shù)時(shí)并不執(zhí)行函數(shù)體。如:

            [html] view plaincopy#include

            int print()

            {

            printf("Hello bigloomy!");

            return 0;

            }

            void main()

            {

            printf("sizeof(print()):t%dn",sizeof(print()));

            return ;

            }

            運(yùn)行結(jié)果為:



            [html] view plaincopysizeof(print()): 4

            Press any key to continue

            從結(jié)果我們可以看出print()函數(shù)并沒(méi)有被調(diào)用。

            接下來(lái)我們來(lái)看看linux內(nèi)核鏈表里的一個(gè)宏:

            #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

            對(duì)這個(gè)宏的講解我們大致可以分為以下4步進(jìn)行講解:

            1、( (TYPE *)0 ) 0地址強(qiáng)制 "轉(zhuǎn)換" 為 TYPE結(jié)構(gòu)類型的指針;

            2、((TYPE *)0)->MEMBER 訪問(wèn)TYPE結(jié)構(gòu)中的MEMBER數(shù)據(jù)成員;

            3、&( ( (TYPE *)0 )->MEMBER)取出TYPE結(jié)構(gòu)中的數(shù)據(jù)成員MEMBER的地址;

            4、(size_t)(&(((TYPE*)0)->MEMBER))結(jié)果轉(zhuǎn)換為size_t類型。

            宏offsetof的巧妙之處在于將0地址強(qiáng)制轉(zhuǎn)換為 TYPE結(jié)構(gòu)類型的指針,TYPE結(jié)構(gòu)以內(nèi)存空間首地址0作為起始地址,則成員地址自然為偏移地址??赡苡械淖x者會(huì)想是不是非要用0呢?當(dāng)然不是,我們僅僅是為了計(jì)算的簡(jiǎn)便。也可以使用是他的值,只是算出來(lái)的結(jié)果還要再減去該數(shù)值才是偏移地址。來(lái)看看下面的代碼:

            [cpp] view plaincopy#include

            #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)4)->MEMBER)

            typedef struct stu1

            {

            int a;

            int b;

            }stu1;

            void main()

            {

            printf("offsetof(stu1,a):t%dn",offsetof(stu1,a)-4);

            printf("offsetof(stu1,b):t%dn",offsetof(stu1,b)-4);

            }

            運(yùn)行結(jié)果為:



            [cpp] view plaincopyoffsetof(stu1,a): 0

            offsetof(stu1,b): 4

            Press any key to continue

            為了讓讀者加深印象,我們這里在代碼中沒(méi)有使用0,而是使用的4,所以在最終計(jì)算出的結(jié)果部分減去了一個(gè)4才是偏移地址,當(dāng)然實(shí)際使用中我們都是用的是0。

            懂了上面的宏offsetof之后我們?cè)賮?lái)看看下面的代碼:

            [cpp] view plaincopy#include

            #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

            typedef struct stu1

            {

            int a;

            char b[1];

            int c;

            }stu1;

            void main()

            {

            printf("offsetof(stu1,a):t%dn",offsetof(stu1,a));

            printf("offsetof(stu1,b):t%dn",offsetof(stu1,b));

            printf("offsetof(stu1,c):t%dn",offsetof(stu1,c));

            printf("sizeof(stu1) :t%dn",sizeof(stu1));

            }

            運(yùn)行結(jié)果為:



            [cpp] view plaincopyoffsetof(stu1,a): 0

            offsetof(stu1,b): 4

            offsetof(stu1,c): 8

            sizeof(stu1) : 12

            Press any key to continue

            對(duì)于字節(jié)對(duì)齊不了解的讀者可能有疑惑的是c的偏移量怎么會(huì)是8和結(jié)構(gòu)體的大小怎么會(huì)是12呢?因該是sizeof(int)+sizeof(char)+sizeof(int)=9。其實(shí)這是編譯器對(duì)變量存儲(chǔ)的一個(gè)特殊處理。為了提高CPU的存儲(chǔ)速度,編譯器對(duì)一些變量的起始地址做了對(duì)齊處理。在默認(rèn)情況下,編譯器規(guī)定各成員變量存放的起始地址相對(duì)于結(jié)構(gòu)的起始地址的偏移量必須為該變量的類型所占用的字節(jié)數(shù)的倍數(shù)?,F(xiàn)在來(lái)分析下上面的代碼,如果我們假定a的起始地址為0,它占用了4個(gè)字節(jié),那么接下來(lái)的空閑地址就是4,是1的倍數(shù),滿足要求,所以b存放的起始地址是4,占用一個(gè)字節(jié)。接下來(lái)的空閑地址為5,而c是int變量,占用4個(gè)字節(jié),5不是4的整數(shù)倍,所以向后移動(dòng),找到離5最近的8作為存放c的起始地址,c也占用4字節(jié),所以最后使得結(jié)構(gòu)體的大小為12?,F(xiàn)在我們?cè)賮?lái)看看下面的代碼:

            [cpp] view plaincopy#include

            typedef struct stu1

            {

            char array[7];

            }stu1;

            typedef struct stu2

            {

            double fa;

            }stu2;

            typedef struct stu3

            {

            stu1 s;

            char str;

            }stu3;

            typedef struct stu4

            {

            stu2 s;

            char str;

            }stu4;

            void main()

            {

            printf("sizeof(stu1) :t%dn",sizeof(stu1));

            printf("sizeof(stu2) :t%dn",sizeof(stu2));

            printf("sizeof(stu3) :t%dn",sizeof(stu3));

            printf("sizeof(stu4) :t%dn",sizeof(stu4));

            }

            運(yùn)行結(jié)果為:



            [cpp] view plaincopysizeof(stu1) : 7

            sizeof(stu2) : 8

            sizeof(stu3) : 8

            sizeof(stu4) : 16

            Press any key to continue

            分析下上面我們的運(yùn)行結(jié)果,重點(diǎn)是struct stu3和struct stu4,在struct stu3中使用的是一個(gè)字節(jié)對(duì)齊,因?yàn)樵趕tu1和stu3中都只有一個(gè)char類型,在struct stu3中我們定義了一個(gè)stu1類型的 s,而stu1所占的大小為7,所以加上加上接下來(lái)的一個(gè)字節(jié)str,sizeof(stu3)為8。在stu4中,由于我們定義了一個(gè)stu2類型的s,而s是一個(gè)double類型的變量,占用8字節(jié),所以接下來(lái)在stu4中采用的是8字節(jié)對(duì)齊。如果我們此時(shí)假定stu4中的s從地址0開(kāi)始存放,占用8個(gè)字節(jié),接下來(lái)的空閑地址就是8,根據(jù)我們上面的講解可知?jiǎng)偤每梢栽诖舜娣舠tr。所以變量都分配完空間后stu4結(jié)構(gòu)體所占的字節(jié)數(shù)為9,但9不是結(jié)構(gòu)體的邊界數(shù),也就是說(shuō)我們要求分配的字節(jié)數(shù)為結(jié)構(gòu)體中占用空間最大的類型所占用的字節(jié)數(shù)的整數(shù)倍,在這里也就是double類型所占用的字節(jié)數(shù)8的整數(shù)倍,所以接下來(lái)還要再分配7個(gè)字節(jié)的空間,該7個(gè)字節(jié)的空間沒(méi)有使用,由編譯器自動(dòng)填充,沒(méi)有存放任何有意義的東西。

            當(dāng)然我們也可以使用預(yù)編譯指令#pragma pack (value)來(lái)告訴編譯器,使用我們指定的對(duì)齊值來(lái)取代缺省的。接下來(lái)我們來(lái)看看一段代碼。

            [cpp] view plaincopy#include

            #pragma pack (1) /*指定按1字節(jié)對(duì)齊*/

            typedef union stu1

            {

            char str[10];

            int b;

            }stu1;

            #pragma pack () /*取消指定對(duì)齊,恢復(fù)缺省對(duì)齊*/

            typedef union stu2

            {

            char str[10];

            int b;

            }stu2;

            void main()

            {

            printf("sizeof(stu1) :t%dn",sizeof(stu1));

            printf("sizeof(stu2) :t%dn",sizeof(stu2));

            }

            運(yùn)行結(jié)果為:



            [cpp] view plaincopysizeof(stu1) : 10

            sizeof(stu2) : 12

            Press any key to continue

            現(xiàn)在來(lái)分析下上面的代碼。由于之前我們一直都在使用struct,所以在這里我們特地例舉了一個(gè)union的代碼來(lái)分析下,我們大家都知道union的大小取決于它所有的成員中占用空間最大的一個(gè)成員的大小。由于在union stu1中我們使用了1字節(jié)對(duì)齊,所以對(duì)于stu1來(lái)說(shuō)占用空間最大的是char str[10]類型的數(shù)組,,其值為10。為什么stu1為10而stu2卻是12呢?因?yàn)樵趕tu2的上面我們使用了#pragma pack () ,取消指定對(duì)齊,恢復(fù)缺省對(duì)齊。所以由于stu2其中int類型成員的存在,使stu2的對(duì)齊方式變成4字節(jié)對(duì)齊,也就是說(shuō),stu2的大小必須在4的對(duì)界上,換句話說(shuō)就是stu2的大小要是4的整數(shù)倍,所以占用的空間變成了12。

          linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)

          c語(yǔ)言相關(guān)文章:c語(yǔ)言教程


          linux相關(guān)文章:linux教程




          關(guān)鍵詞: C語(yǔ)言 字節(jié)

          評(píng)論


          相關(guān)推薦

          技術(shù)專區(qū)

          關(guān)閉