<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > arm 結(jié)構(gòu)體對齊問題

          arm 結(jié)構(gòu)體對齊問題

          作者: 時間:2016-11-09 來源:網(wǎng)絡(luò) 收藏
          Arm結(jié)構(gòu)體gcc內(nèi)存邊界對齊問題
          這段時間移植公司的linux i386程序到Arm linux平臺,本以為是件工作量很小的事情,以為只要改幾個驅(qū)動程序就OK了,沒想到在應(yīng)用程序這一塊卡了很長時間。其中最煩的事情就莫過于結(jié)構(gòu)體內(nèi)存邊界對齊了。搞了這么久,終于終結(jié)了一些小經(jīng)驗。

          默認情況下,在32位cpu里,gcc對于結(jié)構(gòu)體的對齊方式是按照四個字節(jié)來對齊的??匆韵陆Y(jié)構(gòu)體

          typedef struct pack{
          char a;
          int b;
          short c;
          }pack;

          對于Pack結(jié)構(gòu)體,默認情況下在arm/386平臺下(別的平臺沒試過)sizeof(pack)=12,求解過程如下:

          sizeof(char)=1;

          下一個int b,由于是四個字節(jié),要求b的開始地址從32的整數(shù)倍開始,故需要在a后面填充3個沒用的字節(jié),記為dump(3),sizeof(b)=4,此時相當于結(jié)構(gòu)體擴充為

          char a;
          char dump(3);
          int b;

          看short c,現(xiàn)在c的前面有8個字節(jié),c是兩個字節(jié),c的開始地址是從16的整數(shù)開始,在b前面不需再加東西.此時對于結(jié)構(gòu)體來說,sizeof(pack)=10,但是這不是最終結(jié)果,最后總的字節(jié)數(shù)也要能被4個字節(jié)整除,所以還需在short c后面再加

          dump(2);

          故總的字節(jié)數(shù)為12.

          當然以上說的只是簡單的情況,下面談?wù)凙rm,x86在gcc里關(guān)于內(nèi)存邊界字節(jié)對齊的區(qū)別.對于同樣的結(jié)構(gòu)體,在386下

          #prama pack(1)

          后,sizeof(pack)=1 4 2=7

          而在arm下同樣的操作sizeof(pack)=1 4 2 1=8,即雖然b根a之間不要填充但總的長度必須要是4的整數(shù)倍.

          在ARM 下要使結(jié)構(gòu)體按指定字節(jié)對齊,可行的方法

          1.在makefile里加-fpack-struct 選項,這樣的話對所有的結(jié)構(gòu)按一字節(jié)對齊.

          不得不說,確實有那么些質(zhì)量較差的程序可能需要你部分自然對齊,部分一字 節(jié)對齊,此時

          2. typedef struct pack{

          }__attribute__((packed))

          可利用__attribute__屬性

          當然最后的方式,還是自己去看ARM體系結(jié)構(gòu)與gcc編譯選項了。
          ------------------------------------------------------------------------------------------------------------
          淺談結(jié)構(gòu)體對齊問題
          #include

          int main() {
          struct ms {
          double x;
          char a;
          int y;
          };
          // }__attribute__((packed));

          printf("%d/n", sizeof(struct ms));
          return 0;
          }

          linux上運行,結(jié)果為16;如果采用注釋的那一行,則結(jié)果為13
          原文::http://dev.csdn.net/article/48/48195.shtm
          什么是內(nèi)存對齊

          考慮下面的結(jié)構(gòu):

          struct foo
          {
          char c1;
          short s;
          char c2;
          int i;
          };

          假設(shè)這個結(jié)構(gòu)的成員在內(nèi)存中是緊湊排列的,假設(shè)c1的地址是0,那么s的地址就應(yīng)該是1,c2的地址就是3,i的地址就是4。也就是
          c1 00000000, s 00000001, c2 00000003, i 00000004。

          可是,我們在Visual c/c++ 6中寫一個簡單的程序:

          struct foo a;
          printf("c1 %p, s %p, c2 %p, i %p/n",
          (unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
          (unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
          (unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
          (unsigned int)(void*)&a.i - (unsigned int)(void*)&a);
          運行,輸出:
          c1 00000000, s 00000002, c2 00000004, i 00000008。

          為什么會這樣?這就是內(nèi)存對齊而導致的問題。

          為什么會有內(nèi)存對齊

          以下內(nèi)容節(jié)選自《Intel Architecture 32 Manual》。
          字,雙字,和四字在自然邊界上不需要在內(nèi)存中對齊。(對字,雙字,和四字來說,自然邊界分別是偶數(shù)地址,可以被4整除的地址,和可以被8整除的地址。)
          無論如何,為了提高程序的性能,數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問;然而,對齊的內(nèi)存訪問僅需要一次訪問。
          一個字或雙字操作數(shù)跨越了4字節(jié)邊界,或者一個四字操作數(shù)跨越了8字節(jié)邊界,被認為是未對齊的,從而需要兩次總線周期來訪問內(nèi)存。一個字起始地址是奇數(shù)但卻沒有跨越字邊界被認為是對齊的,能夠在一個總線周期中被訪問。
          某些操作雙四字的指令需要內(nèi)存操作數(shù)在自然邊界上對齊。如果操作數(shù)沒有對齊,這些指令將會產(chǎn)生一個通用保護異常(#GP)。雙四字的自然邊界是能夠被16 整除的地址。其他的操作雙四字的指令允許未對齊的訪問(不會產(chǎn)生通用保護異常),然而,需要額外的內(nèi)存總線周期來訪問內(nèi)存中未對齊的數(shù)據(jù)。

          編譯器對內(nèi)存對齊的處理

          缺省情況下,c/c++編譯器默認將結(jié)構(gòu)、棧中的成員數(shù)據(jù)進行內(nèi)存對齊。因此,上面的程序輸出就變成了:
          c1 00000000, s 00000002, c2 00000004, i 00000008。
          編譯器將未對齊的成員向后移,將每一個都成員對齊到自然邊界上,從而也導致了整個結(jié)構(gòu)的尺寸變大。盡管會犧牲一點空間(成員之間有空洞),但提高了性能。
          也正是這個原因,我們不可以斷言sizeof(foo) == 8。在這個例子中,sizeof(foo) == 12。

          如何避免內(nèi)存對齊的影響

          那么,能不能既達到提高性能的目的,又能節(jié)約一點空間呢?有一點小技巧可以使用。比如我們可以將上面的結(jié)構(gòu)改成:

          struct bar
          {
          char c1;
          char c2;
          short s;
          int i;
          };
          這樣一來,每個成員都對齊在其自然邊界上,從而避免了編譯器自動對齊。在這個例子中,sizeof(bar) == 8。

          這個技巧有一個重要的作用,尤其是這個結(jié)構(gòu)作為API的一部分提供給第三方開發(fā)使用的時候。第三方開發(fā)者可能將編譯器的默認對齊選項改變,從而造成這個結(jié)構(gòu)在你的發(fā)行的DLL中使用某種對齊方式,而在第三方開發(fā)者哪里卻使用另外一種對齊方式。這將會導致重大問題。
          比如,foo結(jié)構(gòu),我們的DLL使用默認對齊選項,對齊為
          c1 00000000, s 00000002, c2 00000004, i 00000008,同時sizeof(foo) == 12。
          而第三方將對齊選項關(guān)閉,導致
          c1 00000000, s 00000001, c2 00000003, i 00000004,同時sizeof(foo) == 8。

          如何使用c/c++中的對齊選項

          vc6中的編譯選項有 /Zp[1|2|4|8|16] ,/Zp1表示以1字節(jié)邊界對齊,相應(yīng)的,/Zpn表示以n字節(jié)邊界對齊。n字節(jié)邊界對齊的意思是說,一個成員的地址必須安排在成員的尺寸的整數(shù)倍地址上或者是n的整數(shù)倍地址上,取它們中的最小值。也就是:
          min ( sizeof ( member ), n)
          實際上,1字節(jié)邊界對齊也就表示了結(jié)構(gòu)成員之間沒有空洞。
          /Zpn選項是應(yīng)用于整個工程的,影響所有的參與編譯的結(jié)構(gòu)。
          要使用這個選項,可以在vc6中打開工程屬性頁,c/c++頁,選擇Code Generation分類,在Struct member alignment可以選擇。

          要專門針對某些結(jié)構(gòu)定義使用對齊選項,可以使用#pragma pack編譯指令。指令語法如下:
          #pragma pack( [ show ] | [ push | pop ] [, identifier ] , n )
          意義和/Zpn選項相同。比如:

          #pragma pack(1)
          struct foo_pack
          {
          char c1;
          short s;
          char c2;
          int i;
          };
          #pragma pack()

          棧內(nèi)存對齊

          我們可以觀察到,在vc6中棧的對齊方式不受結(jié)構(gòu)成員對齊選項的影響。(本來就是兩碼事)。它總是保持對齊,而且對齊在4字節(jié)邊界上。

          驗證代碼

          #include

          struct foo
          {
          char c1;
          short s;
          char c2;
          int i;
          };

          struct bar
          {
          char c1;
          char c2;
          short s;
          int i;
          };

          #pragma pack(1)
          struct foo_pack
          {
          char c1;
          short s;
          char c2;
          int i;
          };
          #pragma pack()

          int main(int argc, char* argv[])
          {
          char c1;
          short s;
          char c2;
          int i;

          struct foo a;
          struct bar b;
          struct foo_pack p;

          printf("stack c1 %p, s %p, c2 %p, i %p/n",
          (unsigned int)(void*)&c1 - (unsigned int)(void*)&i,
          (unsigned int)(void*)&s - (unsigned int)(void*)&i,
          (unsigned int)(void*)&c2 - (unsigned int)(void*)&i,
          (unsigned int)(void*)&i - (unsigned int)(void*)&i);

          printf("struct foo c1 %p, s %p, c2 %p, i %p/n",
          (unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
          (unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
          (unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
          (unsigned int)(void*)&a.i - (unsigned int)(void*)&a);

          printf("struct bar c1 %p, c2 %p, s %p, i %p/n",
          (unsigned int)(void*)&b.c1 - (unsigned int)(void*)&b,
          (unsigned int)(void*)&b.c2 - (unsigned int)(void*)&b,
          (unsigned int)(void*)&b.s - (unsigned int)(void*)&b,
          (unsigned int)(void*)&b.i - (unsigned int)(void*)&b);

          printf("struct foo_pack c1 %p, s %p, c2 %p, i %p/n",
          (unsigned int)(void*)&p.c1 - (unsigned int)(void*)&p,
          (unsigned int)(void*)&p.s - (unsigned int)(void*)&p,
          (unsigned int)(void*)&p.c2 - (unsigned int)(void*)&p,
          (unsigned int)(void*)&p.i - (unsigned int)(void*)&p);

          printf("sizeof foo is %d/n", sizeof(foo));
          printf("sizeof bar is %d/n", sizeof(bar));
          printf("sizeof foo_pack is %d/n", sizeof(foo_pack));

          return 0;
          }
          -----------------------------------------------------------------------------------------------------------在結(jié)構(gòu)中,編譯器為結(jié)構(gòu)的每個成員按其自然對界條件分配空間;各個成員按照它們被聲明的順序在內(nèi)存中順序存儲,第一個成員的地址和整個結(jié)構(gòu)的地址相同。在缺省情況下,c編譯器為每一個變量或是數(shù)據(jù)單元按其自然對界條件分配空間

          例如,下面的結(jié)構(gòu)各成員空間分配情況

          struct test {
          char x1;
          short x2;
          float x3;
          char x4;
          };
            
                           
            結(jié)構(gòu)的第一個成員x1,其偏移地址為0,占據(jù)了第1個字節(jié)。第二個成員x2為short類型,其起始地址必須2字節(jié)對界,因此,編譯器在x2和x1之間填充了一個空字節(jié)。結(jié)構(gòu)的第三個成員x3和第四個成員x4恰好落在其自然對界地址上,在它們前面不需要額外的填充字節(jié)。在test結(jié)構(gòu)中,成員x3要求 4字節(jié)對界,是該結(jié)構(gòu)所有成員中要求的最大對界單元,因而test結(jié)構(gòu)的自然對界條件為4字節(jié),編譯器在成員x4后面填充了3個空字節(jié)。整個結(jié)構(gòu)所占據(jù)空間為12字節(jié)。

          現(xiàn)在你知道怎么回事了吧?

          更改c編譯器的缺省分配策略
            一般地,可以通過下面的兩種方法改變?nèi)笔〉膶鐥l件:
            · 使用偽指令#pragma pack ([n])
            · 在編譯時使用命令行參數(shù)
          #pragma pack ([n])偽指令允許你選擇編譯器為數(shù)據(jù)分配空間所采取的對界策略:

            
                         
            
            例如,在使用了#pragma pack (1)偽指令后,test結(jié)構(gòu)各成員的空間分配情況就是按照一個字節(jié)對齊了
          #pragma pack(push) //保存對齊狀態(tài)
          #pragma pack(1)

          #pragma pack(pop)


          評論


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

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();