內存對齊


什么是內存對齊

現代計算機中內存空間都是按照byte划分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定變量的時候經常在特定的內存地址訪問,這就需要各類型數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。

為什么要內存對齊

平台原因:

某些硬件平台只能在某些地址處取某些特定類型的數據,否則拋出硬件異常。

性能原因:

為了訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。

總的來說,內存對齊就是拿空間換取時間的做法,目的是為了讓CPU能一次獲取到數據,從而提升性能。

#pragma pack()

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

 

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

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

 

也可以寫成:

#pragma pack(push,n)

#pragma pack(pop)

 

#pragma pack (n)表示每個成員的對齊單元不大於n(n為2的整數次冪)。這里規定的是上界,只影響對齊單元大於n的成員,對於對齊字節不大於n的成員沒有影響。

 

內存對齊規則

每個特定平台上的編譯器都有自己的默認“對齊系數”(也叫對齊模數)。程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊系數”。

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


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


3、結合1、2顆推斷:當#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。

 

內存對齊實例

#include <stdio.h>
#include <stdint.h>

#pragma pack(1)
typedef struct {
  /*成員對齊*/
   int a;     //長度4 < 1 **按1對齊**;偏移量為0;存放位置區間[0,3]
   char b;    //長度1 = 1 **按1對齊**;偏移量為4;存放位置區間[4]
   short c;   //長度2 > 1 **按1對齊**;偏移量為5;存放位置區間[5,6]
   char d;    //長度1 = 1 **按1對齊**;偏移量為6;存放位置區間[7]
   /*整體對齊*/
   //整體對齊系數 = min(對齊系數1,最大成員長度4) = 1,無需對齊,整體大小為8;
}test_pack1;
#pragma pack()

 
#pragma pack(2)
typedef struct {
   /*成員對齊*/   
int a; //長度4 > 2 **按2對齊**;偏移量為0;存放位置區間[0,3]   char b; //長度1 < 2 **按1對齊**;偏移量為4;存放位置區間[4]   short c; //長度2 = 2 **按2對齊**;偏移量要提升到2的倍數6;存放位置區間[6,7]   char d; //長度1 < 2 **按1對齊**;偏移量為7;存放位置區間[8];共九個字節   /*整體對齊*/   //整體對齊系數 = min(對齊系數2,最大成員長度4) = 2,將9提升到2的倍數10,整體大小為10; }test_pack2; #pragma pack() #pragma pack(4) typedef struct {
   /*成員對齊*/   
int a; //長度4 = 4 **按4對齊**;偏移量為0;存放位置區間[0,3]   char b; //長度1 < 4 **按1對齊**;偏移量為4;存放位置區間[4]   short c; //長度2 < 4 **按2對齊**;偏移量要提升到2的倍數6;存放位置區間[6,7]   char d; //長度1 < 4 **按1對齊**;偏移量為7;存放位置區間[8];總大小為9   /*整體對齊*/   //整體對齊系數 = min(對齊系數4,最大成員長度4) = 4,將9提升到4的倍數12,整體大小為12; }test_pack4; #pragma pack() #pragma pack(8) typedef struct {
   /*成員對齊*/   
int a; //長度4 < 8 **按4對齊**;偏移量為0;存放位置區間[0,3]   char b; //長度1 < 8 **按1對齊**;偏移量為4;存放位置區間[4]   short c; //長度2 < 8 **按2對齊**;偏移量要提升到2的倍數6;存放位置區間[6,7]   char d; //長度1 < 8 **按1對齊**;偏移量為7;存放位置區間[8],總大小為9   /*整體對齊*/   //整體對齊系數 = min(對齊系數8,最大成員長度4) = 4,將9提升到4的倍數12,整體大小為12; }test_pack8; #pragma pack() test_pack1 pack1 = {0x11111111, 0x22, 0x3333, 0x44}; uint8_t *ptrPack1 = (uint8_t *)&pack1; test_pack2 pack2 = {0x11111111, 0x22, 0x3333, 0x44}; uint8_t *ptrPack2 = (uint8_t *)&pack2; test_pack4 pack4 = {0x11111111, 0x22, 0x3333, 0x44}; uint8_t *ptrPack4 = (uint8_t *)&pack4; test_pack8 pack8 = {0x11111111, 0x22, 0x3333, 0x44}; uint8_t *ptrPack8 = (uint8_t *)&pack8; int main(int argc, char *argv[]) {   int i = 0;   printf("#pragma pack(1) \tsize:%2d,\t", sizeof(pack1));   for(i=0; i<sizeof(pack1); i++)   { printf("%02x", ptrPack1[i]);   }   printf("\r\n");   printf("#pragma pack(2) \tsize:%2d,\t", sizeof(pack2));   for(i=0; i<sizeof(pack2); i++)   { printf("%02x", ptrPack2[i]);   }   printf("\r\n");   printf("#pragma pack(4) \tsize:%2d,\t", sizeof(pack4));   for(i=0; i<sizeof(pack4); i++)   { printf("%02x", ptrPack4[i]);   }   printf("\r\n");   printf("#pragma pack(8) \tsize:%d,\t", sizeof(pack8));   for(i=0; i<sizeof(pack8); i++)   { printf("%02x", ptrPack8[i]);   }   printf("\r\n");
  
return 0; }

 

該段測試實例測試同一結構體,為了觀察方便,成員的每一個字節值按順序分別為十六進制0xaa、0xbb、0xcc、0xdd。

分別進行1、2、4、8字節對齊方式對齊,計算其占用內存大小及在內存的排列的結果,具體對齊步驟見代碼注釋部分。

  

什么時候需要指定內存對齊

一般情況下都不需要對編譯器進行的內存對齊規則進行修改,因為這樣會降低程序的性能,只有這個結構在涉及到對外交互的情況下,比如這個結構需要對外協議交互、寫入文件等。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM