乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      一文輕松理解內(nèi)存對(duì)齊

       C語(yǔ)言與CPP編程 2021-12-15

      什么是內(nèi)存對(duì)齊

      元素是按照定義順序一個(gè)一個(gè)放到內(nèi)存中去的,但并不是緊密排列的。從結(jié)構(gòu)體存儲(chǔ)的首地址開(kāi)始,每個(gè)元素放置到內(nèi)存中時(shí),它都會(huì)認(rèn)為內(nèi)存是按照自己的大小(通常它為4或8)來(lái)劃分的,因此元素放置的位置一定會(huì)在自己寬度的整數(shù)倍上開(kāi)始,這就是所謂的內(nèi)存對(duì)齊。

      編譯器為程序中的每個(gè)“數(shù)據(jù)單元”安排在適當(dāng)?shù)奈恢蒙?。C語(yǔ)言允許你干預(yù)“內(nèi)存對(duì)齊”。如果你想了解更加底層的秘密,“內(nèi)存對(duì)齊”對(duì)你就不應(yīng)該再模糊了。

      以一個(gè)例子開(kāi)始了解

      理論上,int占4byte,char占一個(gè)byte,那么將它們放到一個(gè)結(jié)構(gòu)體中應(yīng)該占4+1=5byte;但是實(shí)際上,通過(guò)運(yùn)行程序得到的結(jié)果是8 byte,這就是內(nèi)存對(duì)齊所導(dǎo)致的。

      #include<stdio.h>
      
      struct{
          int x;
          char y;
      }Test;
      
      int main()
      {
          printf("%d\n",sizeof(Test)); // 輸出8不是5
          return 0;
      }
      

      為什么要內(nèi)存對(duì)齊

      1. 平臺(tái)原因(移植原因):不是所有的硬件平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類型的數(shù)據(jù),否則拋出硬件異常。

      2. 性能原因:數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。原因在于,為了訪問(wèn)未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問(wèn);而對(duì)齊的內(nèi)存訪問(wèn)僅需要一次訪問(wèn)。

      • 假如沒(méi)有內(nèi)存對(duì)齊機(jī)制,數(shù)據(jù)可以任意存放,現(xiàn)在一個(gè)int變量存放在從地址1開(kāi)始的聯(lián)系四個(gè)字節(jié)地址中,該處理器去取數(shù)據(jù)時(shí),要先從0地址開(kāi)始讀取第一個(gè)4字節(jié)塊,剔除不想要的字節(jié)(0地址),然后從地址4開(kāi)始讀取下一個(gè)4字節(jié)塊,同樣剔除不要的數(shù)據(jù)(5,6,7地址),最后留下的兩塊數(shù)據(jù)合并放入寄存器。這需要做很多工作。

      • 現(xiàn)在有了內(nèi)存對(duì)齊的,int類型數(shù)據(jù)只能存放在按照對(duì)齊規(guī)則的內(nèi)存中,比如說(shuō)0地址開(kāi)始的內(nèi)存。那么現(xiàn)在該處理器在取數(shù)據(jù)時(shí)一次性就能將數(shù)據(jù)讀出來(lái)了,而且不需要做額外的操作,提高了效率。

      內(nèi)存對(duì)齊規(guī)則

      1. 基本類型的對(duì)齊值就是其sizeof值;

      2. 數(shù)據(jù)成員對(duì)齊規(guī)則:結(jié)構(gòu)(struct)(或聯(lián)合(union))的數(shù)據(jù)成員,第一個(gè)數(shù)據(jù)成員放在offset為0的地方,以后每個(gè)數(shù)據(jù)成員的對(duì)齊按照#pragma pack指定的數(shù)值和這個(gè)數(shù)據(jù)成員自身長(zhǎng)度中,比較小的那個(gè)進(jìn)行;

      3. 結(jié)構(gòu)(或聯(lián)合)的整體對(duì)齊規(guī)則:在數(shù)據(jù)成員完成各自對(duì)齊之后,結(jié)構(gòu)(或聯(lián)合)本身也要進(jìn)行對(duì)齊,對(duì)齊將按照#pragma pack指定的數(shù)值和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員長(zhǎng)度中,比較小的那個(gè)進(jìn)行;

      //2020.05.12 公眾號(hào):C語(yǔ)言與CPP編程
      #include<stdio.h>
      struct
      {
          int i;    
          char c1;  
          char c2;  
      }Test1;
      
      struct{
          char c1;  
          int i;    
          char c2;  
      }Test2;
      
      struct{
          char c1;  
          char c2; 
          int i;    
      }Test3;
      
      int main()
      {
          printf("%d\n",sizeof(Test1));  // 輸出8
          printf("%d\n",sizeof(Test2));  // 輸出12
          printf("%d\n",sizeof(Test3));  // 輸出8
          return 0;
      }
      

      默認(rèn)#pragma pack(4),且結(jié)構(gòu)體中最長(zhǎng)的數(shù)據(jù)類型為4個(gè)字節(jié),所以有效對(duì)齊單位為4字節(jié),下面根據(jù)上面所說(shuō)的規(guī)則以第二個(gè)結(jié)構(gòu)體來(lái)分析其內(nèi)存布局: 首先使用規(guī)則1,對(duì)成員變量進(jìn)行對(duì)齊:

      • sizeof(c1) = 1 <= 4(有效對(duì)齊位),按照1字節(jié)對(duì)齊,占用第0單元;

      • sizeof(i) = 4 <= 4(有效對(duì)齊位),相對(duì)于結(jié)構(gòu)體首地址的偏移要為4的倍數(shù),占用第4,5,6,7單元;

      • sizeof(c2) = 1 <= 4(有效對(duì)齊位),相對(duì)于結(jié)構(gòu)體首地址的偏移要為1的倍數(shù),占用第8單元;

      然后使用規(guī)則2,對(duì)結(jié)構(gòu)體整體進(jìn)行對(duì)齊:

      第二個(gè)結(jié)構(gòu)體中變量i占用內(nèi)存最大占4字節(jié),而有效對(duì)齊單位也為4字節(jié),兩者較小值就是4字節(jié)。因此整體也是按照4字節(jié)對(duì)齊。由規(guī)則1得到s2占9個(gè)字節(jié),此處再按照規(guī)則2進(jìn)行整體的4字節(jié)對(duì)齊,所以整個(gè)結(jié)構(gòu)體占用12個(gè)字節(jié)。

      根據(jù)上面的分析,不難得出上面例子三個(gè)結(jié)構(gòu)體的內(nèi)存布局如下:

      例子三個(gè)結(jié)構(gòu)體的內(nèi)存布局

      例子三個(gè)結(jié)構(gòu)體的內(nèi)存布局

      更改C編譯器的缺省字節(jié)對(duì)齊方式:

      在缺省情況下,C編譯器為每一個(gè)變量或是數(shù)據(jù)單元按其自然對(duì)界條件分配空間。一般地,可以通過(guò)下面的方法來(lái)改變?nèi)笔〉膶?duì)界條件:

      • 使用偽指令#pragma pack (n),C編譯器將按照n個(gè)字節(jié)對(duì)齊。

      • 使用偽指令#pragma pack (),取消自定義字節(jié)對(duì)齊方式。

      另外,還有如下的一種方式:

      • __attribute((aligned (n))),讓所作用的結(jié)構(gòu)成員對(duì)齊在n字節(jié)自然邊界上。如果結(jié)構(gòu)中有成員的長(zhǎng)度大于n,則按照最大成員的長(zhǎng)度來(lái)對(duì)齊。

      • attribute((packed)),取消結(jié)構(gòu)在編譯過(guò)程中的優(yōu)化對(duì)齊,按照實(shí)際占用字節(jié)數(shù)進(jìn)行對(duì)齊。

      不同平臺(tái)上編譯器的 pragma pack 默認(rèn)值不同。而我們可以通過(guò)預(yù)編譯命令#pragma pack(n), n= 1,2,4,8,16來(lái)改變對(duì)齊系數(shù)。

      例如,對(duì)于上個(gè)例子的三個(gè)結(jié)構(gòu)體,如果前面加上#pragma pack(1),那么此時(shí)有效對(duì)齊值為1字節(jié),此時(shí)根據(jù)對(duì)齊規(guī)則,不難看出成員是連續(xù)存放的,三個(gè)結(jié)構(gòu)體的大小都是6字節(jié)。

      有效對(duì)齊值為1字節(jié)

      有效對(duì)齊值為1字節(jié)

      如果前面加上#pragma pack(2),有效對(duì)齊值為2字節(jié),此時(shí)根據(jù)對(duì)齊規(guī)則,三個(gè)結(jié)構(gòu)體的大小應(yīng)為6,8,6。內(nèi)存分布圖如下:

      有效對(duì)齊值為2字節(jié)

      有效對(duì)齊值為2字節(jié)

      例子

      請(qǐng)寫出以下代碼的輸出結(jié)果:

      #include<stdio.h>
      struct S1
      {
          int i:8;    
          char j:4;  
          int a:4;  
          double b;
      };
      
      struct S2
      {
          int i:8;    
          char j:4;  
          double b;
          int a:4;  
      };
      
      struct S3
      {
          int i;    
          char j;  
          double b;
          int a;     
      };
      
      int main()
      {
          printf("%d\n",sizeof(S1));  // 輸出8
          printf("%d\n",sizeof(S1);  // 輸出12
          printf("%d\n",sizeof(Test3));  // 輸出8
          return 0;
      }
      

      分析問(wèn)題

      在存儲(chǔ)某些數(shù)據(jù)時(shí),其實(shí)際需求的數(shù)據(jù)長(zhǎng)度可能要小于一個(gè)字節(jié),只占一位或幾位。為了節(jié)省空間,處理方便,在C中引入了另一種結(jié)構(gòu),稱為“位域”或“位段”。

      所謂“位域”,就是把一個(gè)字節(jié)中的“位”按照實(shí)際的需求分成不同的區(qū)域,表明每個(gè)區(qū)域位數(shù)、區(qū)域的域名,并允許程序按照域名進(jìn)行操作。如此就可以把不同的對(duì)象用一個(gè)字節(jié)來(lái)表示。

      位域的定義與結(jié)構(gòu)定義相仿,其形式為:

      struct 位域的結(jié)構(gòu)體名
      {
         //位域列表
      }
      

      位域列表的形式為:【類型說(shuō)明符】 【位域名】:【位域的長(zhǎng)度】例如:

      struct ab
      {
         int a:8;
         int b:2;
         int c:6;
      }
      

      對(duì)于位域的定義,有以下幾點(diǎn)說(shuō)明:

      (1)一個(gè)位域必須存儲(chǔ)在同一個(gè)字節(jié)中,不能跨兩個(gè)字節(jié)。如一個(gè)字節(jié)所??臻g不夠存放另一位域時(shí),應(yīng)從下一單元起存放該位域。也可以有意使某位域從下一單元開(kāi)始。

      例如:

      struct wy
      {
         unsigned a:6;
         unsigned 0;     //空域
         unsigned b:4;   //從一單元開(kāi)始存放
         unsigned c:4;   
      }
      

      在這個(gè)位域定義中,a占第一字節(jié)的6位,后2位填0表示不使用,b從第二字節(jié)開(kāi)始,占用4位,c占用4位。內(nèi)存結(jié)構(gòu)如下圖所示。

      存儲(chǔ)結(jié)構(gòu)圖

      存儲(chǔ)結(jié)構(gòu)圖

      (2)由于位域不允許跨兩個(gè)字節(jié),因此位域的長(zhǎng)度不能大于一個(gè)字節(jié)的長(zhǎng)度,也就是不能超過(guò)8位二進(jìn)位。

      (3)位域可以無(wú)位域名,這時(shí)它只用來(lái)填充或調(diào)整位置。無(wú)名的位域是不能使用的。

      例如:

      struct wk
      {
         int a:1;
         int :2;     //不能使用
         int b:3;  
         int c:2;   
      }
      

      存儲(chǔ)結(jié)構(gòu)圖

      存儲(chǔ)結(jié)構(gòu)圖

      從以上述分析可以看出,位域可以看做是一種結(jié)構(gòu)類型,其特點(diǎn)是成員均按二進(jìn)位分配。

      根據(jù)以上分析可知,在s1中i在相對(duì)0的位置,占8位即第1個(gè)字節(jié)。j就在相對(duì)第2個(gè)字節(jié)的位置。由于一個(gè)位置的字節(jié)數(shù)是4位的倍數(shù),因此不用對(duì)齊,可以就放在那里。a要放在4位倍數(shù)關(guān)系的位置上,因此不用對(duì)齊,就放在那里。

      目前總共是16位,2字節(jié),由于double是8字節(jié)的,因此要在距相對(duì)0位置為8個(gè)字節(jié)的位置處放下。所以從16位開(kāi)始到8個(gè)字節(jié)之間的位置被忽略,直接放在相對(duì)第8字節(jié)的位置,因此,s1總共占16字節(jié)。存儲(chǔ)結(jié)構(gòu)如下圖所示。

      S1存儲(chǔ)結(jié)構(gòu)

      S1存儲(chǔ)結(jié)構(gòu)

      在s2中,每個(gè)數(shù)據(jù)都要對(duì)照結(jié)構(gòu)體內(nèi)最大數(shù)據(jù)的最小公倍數(shù)補(bǔ)齊。如i,j共12位,小于double的8個(gè)字節(jié)需按8字節(jié)補(bǔ)齊,a位也要按8字節(jié)補(bǔ)齊,共24個(gè)字節(jié),存儲(chǔ)結(jié)構(gòu)如下圖所示。

      s2的存儲(chǔ)結(jié)構(gòu)

      s2的存儲(chǔ)結(jié)構(gòu)

      在s3中,i是int型數(shù)據(jù)(按32位機(jī)分析)占4個(gè)字節(jié),j是char型數(shù)據(jù)占一個(gè)字節(jié),a是int型數(shù)據(jù)占4個(gè)字節(jié),b是double型數(shù)據(jù)占8個(gè)字節(jié)。 在此b是最大的數(shù)據(jù)類型,因此i、j、a都要向b的double型對(duì)齊,即i、j、a的數(shù)據(jù)長(zhǎng)度要向b對(duì)齊為8個(gè)字節(jié),四個(gè)數(shù)據(jù)共占據(jù)32個(gè)字節(jié)。s3的存儲(chǔ)結(jié)構(gòu)如下圖所示。

      s3的存儲(chǔ)結(jié)構(gòu)

      s3的存儲(chǔ)結(jié)構(gòu)

      答案

      sizeof(S1)=16

      sizeof(S2)=24

      sizeof(S3)=32

      說(shuō)明:結(jié)構(gòu)體作為一種復(fù)合數(shù)據(jù)類型,其構(gòu)成元素既可以是基本數(shù)據(jù)類型的變量,也可以是一些復(fù)合型類型數(shù)據(jù)。對(duì)此,編譯器會(huì)自動(dòng)進(jìn)行成員變量的對(duì)齊以提高運(yùn)算效率。默認(rèn)情況下,按自然對(duì)齊條件分配空間。各個(gè)成員按照它們被聲明的順序在內(nèi)存中順序存儲(chǔ),第一個(gè)成員的地址和整個(gè)結(jié)構(gòu)的地址相同,向結(jié)構(gòu)體成員中size最大的成員對(duì)齊。

      許多實(shí)際的計(jì)算機(jī)系統(tǒng)對(duì)基本類型數(shù)據(jù)在內(nèi)存中存放的位置有限制,它們會(huì)要求這些數(shù)據(jù)的首地址的值是某個(gè)數(shù)k(通常它為4或8)的倍數(shù),而這個(gè)k則被稱為該數(shù)據(jù)類型的對(duì)齊模數(shù)。

        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

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

        類似文章 更多