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

分享

一文輕松理解內存對齊

 C語言與CPP編程 2021-12-15

什么是內存對齊

元素是按照定義順序一個一個放到內存中去的,但并不是緊密排列的。從結構體存儲的首地址開始,每個元素放置到內存中時,它都會認為內存是按照自己的大小(通常它為4或8)來劃分的,因此元素放置的位置一定會在自己寬度的整數倍上開始,這就是所謂的內存對齊。

編譯器為程序中的每個“數據單元”安排在適當的位置上。C語言允許你干預“內存對齊”。如果你想了解更加底層的秘密,“內存對齊”對你就不應該再模糊了。

以一個例子開始了解

理論上,int占4byte,char占一個byte,那么將它們放到一個結構體中應該占4+1=5byte;但是實際上,通過運行程序得到的結果是8 byte,這就是內存對齊所導致的。

#include<stdio.h>

struct{
    int x;
    char y;
}Test;

int main()
{
    printf("%d\n",sizeof(Test)); // 輸出8不是5
    return 0;
}

為什么要內存對齊

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

  2. 性能原因:數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。

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

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

內存對齊規(guī)則

  1. 基本類型的對齊值就是其sizeof值;

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

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

//2020.05.12 公眾號:C語言與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;
}

默認#pragma pack(4),且結構體中最長的數據類型為4個字節(jié),所以有效對齊單位為4字節(jié),下面根據上面所說的規(guī)則以第二個結構體來分析其內存布局: 首先使用規(guī)則1,對成員變量進行對齊:

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

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

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

然后使用規(guī)則2,對結構體整體進行對齊:

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

根據上面的分析,不難得出上面例子三個結構體的內存布局如下:

例子三個結構體的內存布局

例子三個結構體的內存布局

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

在缺省情況下,C編譯器為每一個變量或是數據單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變缺省的對界條件:

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

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

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

  • __attribute((aligned (n))),讓所作用的結構成員對齊在n字節(jié)自然邊界上。如果結構中有成員的長度大于n,則按照最大成員的長度來對齊。

  • attribute((packed)),取消結構在編譯過程中的優(yōu)化對齊,按照實際占用字節(jié)數進行對齊。

不同平臺上編譯器的 pragma pack 默認值不同。而我們可以通過預編譯命令#pragma pack(n), n= 1,2,4,8,16來改變對齊系數。

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

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

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

如果前面加上#pragma pack(2),有效對齊值為2字節(jié),此時根據對齊規(guī)則,三個結構體的大小應為6,8,6。內存分布圖如下:

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

有效對齊值為2字節(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;
}

分析問題

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

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

位域的定義與結構定義相仿,其形式為:

struct 位域的結構體名
{
   //位域列表
}

位域列表的形式為:【類型說明符】 【位域名】:【位域的長度】例如:

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

對于位域的定義,有以下幾點說明:

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

例如:

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

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

存儲結構圖

存儲結構圖

(2)由于位域不允許跨兩個字節(jié),因此位域的長度不能大于一個字節(jié)的長度,也就是不能超過8位二進位。

(3)位域可以無位域名,這時它只用來填充或調整位置。無名的位域是不能使用的。

例如:

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

存儲結構圖

存儲結構圖

從以上述分析可以看出,位域可以看做是一種結構類型,其特點是成員均按二進位分配。

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

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

S1存儲結構

S1存儲結構

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

s2的存儲結構

s2的存儲結構

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

s3的存儲結構

s3的存儲結構

答案

sizeof(S1)=16

sizeof(S2)=24

sizeof(S3)=32

說明:結構體作為一種復合數據類型,其構成元素既可以是基本數據類型的變量,也可以是一些復合型類型數據。對此,編譯器會自動進行成員變量的對齊以提高運算效率。默認情況下,按自然對齊條件分配空間。各個成員按照它們被聲明的順序在內存中順序存儲,第一個成員的地址和整個結構的地址相同,向結構體成員中size最大的成員對齊。

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

    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多