歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C語言之GCC中支持的內存對齊指令

C語言之GCC中支持的內存對齊指令

日期:2017/3/1 9:09:54   编辑:Linux編程

1:gcc中支持但不推薦使用的指令

#pragma pack() :取消內存對齊訪問
#pragma pack(n) (n=1/2/4/8):按n字節對齊
#pragma pack(2)
struct mystruct1
{
int a;
char b;
short c;
}
struct mystruct2
{
int a;;
double b;
short c;
}
#pragma pack()

以上這部分內容就是按2字節對齊了。
分析:
(1)#pragma是用來指揮編譯器,或者說設置編譯器的對齊方式的。編譯器的默認對齊方式是4,但是有時候我不希望對齊方式是4,而希望是別的(譬如希望1字節對齊,也可能希望是8,甚至可能希望128字節對齊)。
(2)常用的設置編譯器編譯器對齊命令有2種:第一種是#pragma pack(),這種就是設置編譯器1字節對齊(有些人喜歡講:設置編譯器不對齊訪問,還有些講:取消編譯器對齊訪問);第二種是#pragma pack(4),這個括號中的數字就表示我們希望多少字節對齊。
(3)我們需要#prgama pack(n)開頭,以#pragma pack()結尾,定義一個區間,這個區間內的對齊參數就是n。
(4)#prgma pack的方式在很多C環境下都是支持的,但是gcc雖然也可以不過不建議使用。

2:gcc推薦的指令
__attribute__((packed)):取消內存對齊,或者說是1字節對齊
__attribute__((aligned(n))):設定結構體類型整體按n字節對齊,注意是整體而不是這個結構體變量內的元素按n字節對齊

struct mystruct1
{
int a;
char b;
short c;
}__attribute__((packed));

這樣這個結構體類型就按1字節對齊,所以這個結構體類型占7字節
123456 struct mystruct2
{
int a;
char b;
short c;
}__attribute__((aligned(2))) mystr2;

(不能是mystr2 __attribute__((aligned(2))) )
這樣mystruct2這個結構體類型就按2字節對齊
(1)__attribute__((packed))使用時直接放在要進行內存對齊的類型定義的後面,然後它起作用的范圍只有加了這個東西的這一個類型。packed的作用就是取消對齊訪問。
(2)__attribute__((aligned(n)))使用時直接放在要進行內存對齊的類型定義的後面,然後它起作用的范圍只有加了這個東西的這一個類型(只能是加在類型後面,不能加在變量後面)。它的作用是讓整個結構體變量整體進行n字節對齊(注意是結構體變量整體n字節對齊,而不是結構體內各元素也要n字節對齊)

結構體字節對齊

在用sizeof運算符求算某結構體所占空間時,並不是簡單地將結構體中所有元素各自占的空間相加,這裡涉及到內存字節對齊的問題。從理論上講,對於任何變量的訪問都可以從任何地址開始訪問,但是事實上不是如此,實際上訪問特定類型的變量只能在特定的地址訪問,這就需要各個變量在空間上按一定的規則排列,而不是簡單地順序排列,這就是內存對齊。

內存對齊的原因:

1)某些平台只能在特定的地址處訪問特定類型的數據;

2)提高存取數據的速度。比如有的平台每次都是從偶地址處讀取數據,對於一個int型的變量,若從偶地址單元處存放,則只需一個讀取周期即可讀取該變量;但是若從奇地址單元處存放,則需要2個讀取周期讀取該變量。

  在C99標准中,對於內存對齊的細節沒有作過多的描述,具體的實現交由編譯器去處理,所以在不同的編譯環境下,內存對齊可能略有不同,但是對齊的最基本原則是一致的,對於結構體的字節對齊主要有下面兩點:

1)結構體每個成員相對結構體首地址的偏移量(offset)是對齊參數的整數倍,如有需要會在成員之間填充字節。編譯器在為結構體成員開辟空間時,首先檢查預開辟空間的地址相對於結構體首地址的偏移量是否為對齊參數的整數倍,若是,則存放該成員;若不是,則填充若干字節,以達到整數倍的要求。

2)結構體變量所占空間的大小是對齊參數大小的整數倍。如有需要會在最後一個成員末尾填充若干字節使得所占空間大小是對齊參數大小的整數倍。

  注意:在看這兩條原則之前,先了解一下對齊參數這個概念。對於每個變量,它自身有對齊參數,這個自身對齊參數在不同編譯環境下不同。下面列舉的是兩種最常見的編譯環境下各種類型變量的自身對齊參數

  從上面可以發現,在windows(32)/VC6.0下各種類型的變量的自身對齊參數就是該類型變量所占字節數的大小,而在linux(32)/GCC下double類型的變量自身對齊參數是4,是因為linux(32)/GCC下如果該類型變量的長度沒有超過CPU的字長,則以該類型變量的長度作為自身對齊參數,如果該類型變量的長度超過CPU字長,則自身對齊參數為CPU字長,而32位系統其CPU字長是4,所以linux(32)/GCC下double類型的變量自身對齊參數是4,如果是在Linux(64)下,則double類型的自身對齊參數是8。

  除了變量的自身對齊參數外,還有一個對齊參數,就是每個編譯器默認的對齊參數#pragma pack(n),這個值可以通過代碼去設定,如果沒有設定,則取系統的默認值。在windows(32)/VC6.0下,n的取值可以為1、2、4、8,默認情況下為8。在linux(32)/GCC下,n的取值只能為1、2、4,默認情況下為4。注意像DEV-CPP、MinGW等在windows下n的取值和VC的相同。

  了解了這2個概念之後,可以理解上面2條原則了。對於第一條原則,每個變量相對於結構體的首地址的偏移量必須是對齊參數的整數倍,這句話中的對齊參數是取每個變量自身對齊參數和系統默認對齊參數#pragma pack(n)中較小的一個。舉個簡單的例子,比如在結構體A中有變量int a,a的自身對齊參數為4(環境為windows/vc),而VC默認的對齊參數為8,取較小者,則對於a,它相對於結構體A的起始地址的偏移量必須是4的倍數。

  對於第二條原則,結構體變量所占空間的大小是對齊參數的整數倍。這句話中的對齊參數有點復雜,它是取結構體中所有變量的對齊參數的最大值和系統默認對齊參數#pragma pack(n)比較,較小者作為對齊參數。舉個例子假如在結構體A中先後定義了兩個變量int a;double b;對於變量a,它的自身對齊參數為4,而#pragma pack(n)值默認為8,則a的對齊參數為4;b的自身對齊參數為8,而#pragma pack(n)的默認值為8,則b的對齊參數為8。由於a的最終對齊參數為4,b的最終對齊參數為8,那麼兩者較大者是8,然後再拿8和#pragma pack(n)作比較,取較小者作為對齊參數,也就是8,即意味著結構體最終的大小必須能被8整除。

下面是測試例子:

注意:以下例子的測試結果均在windows(32)/VC下測試的,其默認對齊參數為8

/*測試sizeof運算符  2011.10.1*/
 
#include <iostream>
using namespace std;
//#pragma pack(4)    //設置4字節對齊 
//#pragma pack()     //取消4字節對齊 
 
 
typedef struct node1
{
    int a;
    char b;
    short c;
}S1;
 
typedef struct node2
{
    char a;
    int b;
    short c;
}S2;

typedef struct node3
{
    int a;
    short b;
    static int c;
}S3;

typedef struct node4
{
    bool a;
    S1 s1;
    short b;
}S4;

typedef struct node5
{
    bool a;
    S1 s1;
    double b;
    int c;
}S5;



int main(int argc, char *argv[])
{
    cout<<sizeof(char)<<" "<<sizeof(short)<<" "<<sizeof(int)<<" "<<sizeof(float)<<" "<<sizeof(double)<<endl;
    S1 s1;
    S2 s2;
    S3 s3;
    S4 s4;
    S5 s5;
    cout<<sizeof(s1)<<" "<<sizeof(s2)<<" "<<sizeof(s3)<<" "<<sizeof(s4)<<" "<<sizeof(s5)<<endl;
    return 0;
}

下面解釋一下其中的幾個結構體字節分配的情況

比如對於node2

typedef struct node2
{
    char a;
    int b;
    short c;
}S2;

sizeof(S2)=12;

  對於變量a,它的自身對齊參數為1,#pragma pack(n)默認值為8,則最終a的對齊參數為1,為其分配1字節的空間,它相對於結構體起始地址的偏移量為0,能被4整除;

  對於變量b,它的自身對齊參數為4,#pragma pack(n)默認值為8,則最終b的對齊參數為4,接下來的地址相對於結構體的起始地址的偏移量為1,1不能夠整除4,所以需要在a後面填充3字節使得偏移量達到4,然後再為b分配4字節的空間;

  對於變量c,它的自身對齊參數為2,#pragma pack(n)默認值為8,則最終c的對齊參數為2,而接下來的地址相對於結構體的起始地址的偏移量為8,能整除2,所以直接為c分配2字節的空間。

  此時結構體所占的字節數為1+3+4+2=10字節

  最後由於a,b,c的最終對齊參數分別為1,4,2,最大為4,#pragma pack(n)的默認值為8,則結構體變量最後的大小必須能被4整除。而10不能夠整除4,所以需要在後面填充2字節達到12字節。其存儲如下:

  |char|----|----|----| 4字節

|--------int--------| 4字節

|--short--|----|----| 4字節

  總共占12個字節

對於node3,含有靜態數據成員

typedef struct node3
{
    int a;
    short b;
    static int c;
}S3;

  則sizeof(S3)=8.這裡結構體中包含靜態數據成員,而靜態數據成員的存放位置與結構體實例的存儲地址無關(注意只有在C++中結構體中才能含有靜態數據成員,而C中結構體中是不允許含有靜態數據成員的)。其在內存中存儲方式如下:

  |--------int--------| 4字節

  |--short-|----|----| 4字節

  而變量c是單獨存放在靜態數據區的,因此用siezof計算其大小時沒有將c所占的空間計算進來。

而對於node5,裡面含有結構體變量

typedef struct node5
{
    bool a;
    S1 s1;
    double b;
    int c;
}S5; 

sizeof(S5)=32。

  對於變量a,其自身對齊參數為1,#pragma pack(n)為8,則a的最終對齊參數為1,為它分配1字節的空間,它相對於結構體起始地址的偏移量為0,能被1整除;

  對於s1,它的自身對齊參數為4(對於結構體變量,它的自身對齊參數為它裡面各個變量最終對齊參數的最大值),#pragma pack(n)為8,所以s1的最終對齊參數為4,接下來的地址相對於結構體起始地址的偏移量為1,不能被4整除,所以需要在a後面填充3字節達到4,為其分配8字節的空間;

  對於變量b,它的自身對齊參數為8,#pragma pack(n)的默認值為8,則b的最終對齊參數為8,接下來的地址相對於結構體起始地址的偏移量為12,不能被8整除,所以需要在s1後面填充4字節達到16,再為b分配8字節的空間;

  對於變量c,它的自身對齊參數為4,#pragma pack(n)的默認值為8,則c的最終對齊參數為4,接下來相對於結構體其實地址的偏移量為24,能夠被4整除,所以直接為c分配4字節的空間。

  此時結構體所占字節數為1+3+8+4+8+4=28字節。

  對於整個結構體來說,各個變量的最終對齊參數為1,4,8,4,最大值為8,#pragma pack(n)默認值為8,所以最終結構體的大小必須是8的倍數,因此需要在最後面填充4字節達到32字節。其存儲如下:

  |--------bool--------| 4字節

  |---------s1---------| 8字節

  |--------------------| 4字節

  |--------double------| 8字節

  |----int----|---------| 8字節

  另外可以顯示地在程序中使用#pragma pack(n)來設置系統默認的對齊參數,在顯示設置之後,則以設置的值作為標准,其它的和上面所講的類似,就不再贅述了,讀者可以自行上機試驗一下。如果需要取消設置,可以用#pragma pack()來取消。

Copyright © Linux教程網 All Rights Reserved