c/c++內存對齊詳解



c/c++內存對齊詳解

   一、爲什麼會有內存對齊?

      進行內存對齊的作用主要有兩個.

       ( 1 )平臺移植 :   不是所有的硬件平臺都能夠訪問任意地址上的數據,

       ( 2 )性能 :  內存對齊後訪問速度提升了 (對於訪問未對齊的內存,處理器需要作兩次內存訪問;而對齊的內存訪問僅需要一次訪問。)

       爲什麼內存對齊會提升效率?

CPU把內存當成是一塊一塊的,塊的大小可以是2、4、8、16字節等大小。CPU在讀取內存的時候是一塊一塊讀取的。塊大小即memory access granularity:內存讀取粒度。

假設CPU要讀取一個int型4字節大小的數據,看下列2種情況:
(1)數據從0字節開始;
(2)數據從1字節開始;
假設內存讀取粒度是4.

情況(1)的時候,CPU只需一次便可把4字節讀取出來。
但是情況(2)的時候,要複雜一些。這個時候CPU先訪問一次內存,讀取0-3字節進寄存器,再讀取4-7字節進寄存器,然後把0、6、7、8字節的數據刪除掉,最後合併1-4字節的數據。可以看出,如果內存沒有對齊,所進行的操作要複雜得多。

   二、對齊規則?

每個特定平臺上的編譯器都有自己的默認“對齊係數”(也叫對齊模數)。程序員可以通過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的“對齊係數”。
規則:
(1)數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,以後每個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
(2)結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
(3)當#pragma pack的n值等於或超過所有數據成員長度的時候,這個n值的大小將不產生任何效果。

  三、示例代碼

           #include<stdio.h>
struct test
{
char a;
int b;
char c;
double d;
char e;
};
int main(void)
{
printf("%d\n",sizeof(struct test));
return 1;
}
輸出結果爲24
我的執行環境是Debian Linux(內核2.6.18),gcc 4.1.2,其實與在windows下使用vc的原理是一樣的而且不對默認對齊進行修改的話,結果也是一樣的。

四、分析
默認情況下,c/c++一般是設置對齊係數爲4,對於上面的例子也是如此。從上面的結果我們可以看出結構體test佔用的內存大小爲24,而不是1+4+1+8+1=15,那麼24是如何得到的呢?
按照二(1)中的規則對各個成員佔用的內存進行分析如下:
struct test
{
char a; /*長度1 < 4 按1對齊;起始offset=0, 0%1=0,存放區間[0]*/
int b; /*長度4 = 4 按4對齊;起始offset=1, 4%4=0;存放位置區間[4,7] */
char c; /*長度1 < 4 按1對齊;起始offset=8, 8%1=0,存放區間[8]*/
double d;/*長度8 > 4 按4對齊;起始offset=9, 12%4=0,存放區間[12,19]*/*/
char e; /*長度1 < 4 按1對齊;起始offset=20, 20%1=0,存放區間[20]*/
};
在按照二(2)中的規則對結構體整體佔用的內存進行分析:
整體對齊係數爲min(對齊係數,max(成員佔用內存大小))=min(4,8)=4
經過上面的分析test的成員共佔用內存區間[0,20],大小爲21個字節,然後進行整體對齊,需要滿足整體爲整體系數4的倍數,那麼最近的大小就是24了,所以結構體test佔用的內存空間爲24字節。

五、數組,嵌套.

 

#include <iostream>
#include <cstdio>
using namespace std;
#pragma pack(8)
struct Args
{
        char ch; 
        double d;
        short st; 
        char rs[9];
        int i;
} args;
struct Argsa
{
        char ch; 
        Args test;
        char jd[10];
        int i;
}arga;

int main()
{
// cout <<sizeof(char)<<" "<<sizeof(double)<<" "<<sizeof(short)<<" "<<sizeof(int)<<endl;
//cout<<sizeof(long)<<" "<<sizeof(long long)<<" "<<sizeof(float)<<endl;
cout<<"Args:"<<sizeof(args)<<endl;
cout<<""<<(unsigned long)&args.i-(unsigned long)&args.rs<<endl;
cout<<"Argsa:"<<sizeof(arga)<<endl;
cout<<"Argsa(i-jd):"<<(unsigned long)&arga.i -(unsigned long)&arga.jd<<endl;
cout<<"Argsa(jd-test):"<<(unsigned long)&arga.jd-(unsigned long)&arga.test<<endl;
return 0;
}


輸出結果:
Args:32
10
Argsa:56
Argsa(i-jd):12
Argsa(jd-test):32

#include "stdafx.h"
#include "iostream"
using namespace std;
#pragma pack(8)


struct tagOne{

	short s;
	char cArray[4];
	double d;
};

struct tagTwo
{
	char c;
	tagOne tOne;//tOne的大小爲16,在tagTwo中整體以8以齊,所以上面char c補7個字節,

};

int _tmain(int argc, _TCHAR* argv[])
{
	tagTwo temp;
	printf("%d\n",sizeof temp);
	return 0;
}


上面這個例子的輸出結果爲24

當結構體中出現結構體類型的成員時,不會將嵌套的結構體類型的整體長度參與到對齊值計算中,而是以嵌套定義的結構體所使用對齊值進行對齊,



六、其它
1. 在編寫代碼時候可以通過#pragma pack(n),n=1,2,4,8,16來靈活控制內存對齊的係數,當需要關閉內存對齊時,可以使用#pragma pack()實現。
2. 注意事項
內存對齊可以大大的提高編譯器的處理速度,但不是任何時候都是必需的,有的時候不注意的話,還可能出現意想不到的錯誤!最典型的情況就是網絡通信程序的編碼中,一定要在定義結構體或者聯合之前使用#pragma pack()把內存對齊關閉,這是因爲遠程主機通常不知道對方使用的何種對齊方式,通過socket接收的字節流,然後按照字節解析得到對應的結果,如果使用內存對齊,遠程主機很喲可能會得到錯誤的結果!這種情況曾經指導上機時遇到過,而且屬於比較隱蔽的錯誤,debug了好久才發現問題出在這裏。
3. 優化結構體
雖然內存對齊可以提高運行效率,但是卻浪費了內存,在現代PC上通常不會在乎這點小的空間,但是在一些內存很小的嵌入式設備上,可能就要錙銖必較了。其實我們發現在不影響功能的前提下,可以調整成員的順序來減少“內存空洞”帶來的浪費。如果三.中的結構體代碼可以調整爲
struct test
{
char a;
char c;
char e;
int b;
double d;
}
這個時候整個結構體佔用的內存空間將會從上面的24減少到16。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章