深入理解c/c++ 內(nèi)存對(duì)齊
內(nèi)存對(duì)齊,memory alignment.為了提高程序的性能,數(shù)據(jù)結(jié)構(gòu)(尤其是棧)應(yīng)該盡可能地在自然邊界上對(duì)齊。原因在于,為了訪問(wèn)未對(duì)齊的內(nèi)存,處理器需要作兩次內(nèi)存訪問(wèn);然而,對(duì)齊的內(nèi)存訪問(wèn)僅需要一次訪問(wèn)。
內(nèi)存對(duì)齊一般講就是cpu access memory的效率(提高運(yùn)行速度)和準(zhǔn)確性(在一些條件下,如果沒(méi)有對(duì)齊會(huì)導(dǎo)致數(shù)據(jù)不同步現(xiàn)象).依賴(lài)cpu,平臺(tái)和編譯器的不同.一些cpu要求較高(這句話說(shuō)的不準(zhǔn)確,但是確實(shí)依賴(lài)cpu的不同),而有些平臺(tái)已經(jīng)優(yōu)化內(nèi)存對(duì)齊問(wèn)題,不同編譯器的對(duì)齊模數(shù)不同.總的來(lái)說(shuō)內(nèi)存對(duì)齊屬于編譯器的問(wèn)題.
一般情況下不需要理會(huì)內(nèi)存對(duì)齊問(wèn)題,內(nèi)存對(duì)齊是編譯器的事情.但碰到一些問(wèn)題上還是需要理解這個(gè)概念.畢竟c/c++值直接操作內(nèi)存的語(yǔ)言.需要理解程序在內(nèi)存中的分布和運(yùn)行原理.
總之一句話就是:不要讓代碼依賴(lài)內(nèi)存對(duì)齊.
1.原因:為什么需要內(nèi)存對(duì)齊.
1、平臺(tái)原因(移植原因):不是所有的硬件平臺(tái)都能訪問(wèn)任意地址上的任意數(shù)據(jù)的;某些硬件平臺(tái)只能在某些地址處取某些特定類(lè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)。
2.內(nèi)存對(duì)齊的規(guī)則和范例
講述內(nèi)存對(duì)齊之前先看下各種類(lèi)型的大小,和編譯器以及字長(zhǎng)有關(guān)具體在此不多敘述.
具體帖子:http://blog.csdn.net/lyl0625/article/details/7350045
成員的內(nèi)存分配規(guī)律:從結(jié)構(gòu)體的首地址開(kāi)始向后依次為每個(gè)成員尋找第一個(gè)滿(mǎn)足條件的首地址x,該條件是x % N = 0,并且整個(gè)結(jié)構(gòu)的長(zhǎng)度必須為各個(gè)成員所使用的對(duì)齊參數(shù)中最大的那個(gè)值的最小整數(shù)倍,不夠就補(bǔ)空字節(jié)。
結(jié)構(gòu)體中所有成員的對(duì)齊參數(shù)N的最大值稱(chēng)為結(jié)構(gòu)體的對(duì)齊參數(shù)。
1、數(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ù)值(或默認(rèn)值)和這個(gè)數(shù)據(jù)成員類(lèi)型長(zhǎng)度中,比較小的那個(gè)進(jìn)行。在上一個(gè)對(duì)齊后的地方開(kāi)始尋找能被當(dāng)前對(duì)齊數(shù)值整除的地址.
2、結(jié)構(gòu)(或聯(lián)合)的整體對(duì)齊規(guī)則:在數(shù)據(jù)成員完成各自對(duì)齊之后,結(jié)構(gòu)(或聯(lián)合)本身也要進(jìn)行對(duì)齊.主要體現(xiàn)在,最后一個(gè)元素對(duì)齊后,后面是否填補(bǔ)空字節(jié),如果填補(bǔ),填補(bǔ)多少.對(duì)齊將按照#pragma pack指定的數(shù)值(或默認(rèn)值)和結(jié)構(gòu)(或聯(lián)合)最大數(shù)據(jù)成員類(lèi)型長(zhǎng)度中,比較小的那個(gè)進(jìn)行。
3、結(jié)合1、2顆推斷:當(dāng)#pragma pack的n值等于或超過(guò)所有數(shù)據(jù)成員類(lèi)型長(zhǎng)度的時(shí)候,這個(gè)n值的大小將不產(chǎn)生任何效果。
兩點(diǎn)注意:數(shù)組,嵌套結(jié)構(gòu)體.
數(shù)組:
對(duì)齊值為:min(數(shù)組元素類(lèi)型,指定對(duì)齊長(zhǎng)度).但數(shù)組中的元素是連續(xù)存放,存放時(shí)還是按照數(shù)組實(shí)際的長(zhǎng)度.
如char t[9],對(duì)齊長(zhǎng)度為1,實(shí)際占用連續(xù)的9byte.然后根據(jù)下一個(gè)元素的對(duì)齊長(zhǎng)度決定在下一個(gè)元素之前填補(bǔ)多少byte.
嵌套的結(jié)構(gòu)體:
假設(shè)
struct A
{
......
struct B b;
......
};
對(duì)于B結(jié)構(gòu)體在A中的對(duì)齊長(zhǎng)度為:min(B結(jié)構(gòu)體的對(duì)齊長(zhǎng)度,指定的對(duì)齊長(zhǎng)度).
B結(jié)構(gòu)體的對(duì)齊長(zhǎng)度為:上述2中結(jié)構(gòu)整體對(duì)齊規(guī)則中的對(duì)齊長(zhǎng)度.
例子:
VC++6.0中n 默認(rèn)是8個(gè)字節(jié),可以修改這個(gè)設(shè)定的對(duì)齊參數(shù)
也可以采用指令:#pragma pack(xx)控制.
1.基礎(chǔ)例子
#pragma pack(n)
struct A
{
char c; //1byte
double d; //8byte
short s; //2byte
int i; //4byte
};
int main(int argc, char* argv[])
{
A strua;
printf("%len:d\n",sizeof(A));
printf("%d,%d,%d,%d",&strua.c,&strua.d,&strua.s,&strua.i);
return 0;
}
1)n設(shè)置為8byte時(shí)
結(jié)果:len:24,
1245032,1245040,1245048,1245052
內(nèi)存中成員分布如下:
strua.c分配在一個(gè)起始于8的整數(shù)倍的地址1245032(為什么是這樣讀者先自己思考,讀完就會(huì)明白),接下來(lái)要在strua.c之后分配strua.d,由于double為8字節(jié),取N=min(8,8),8字節(jié)來(lái)對(duì)齊,所以從strua.c向后找第一個(gè)能被8整除的地址,所以取1245032+8得1245040, strua.s 為2byte小于參數(shù)n,所以N=min(2,8),即N=2,取2字節(jié)長(zhǎng)度對(duì)齊,所以要從strua.d后面尋找第一個(gè)能被2整除的地址來(lái)存儲(chǔ)strua.s,由于strua.d后面的地址為1245048可以被2整除,所以strua.s緊接著分配,現(xiàn)在來(lái)分配strua.i,int為4byte,小于指定對(duì)齊參數(shù)8byte,所以N=min(4,8)取N=4byte對(duì)齊,strua.s后面第一個(gè)能被4整除地址為1245048+4,所以在1245048+4的位置分配了strua.i,中間補(bǔ)空,同時(shí)由于所有成員的N值的最大值為8,所以整個(gè)結(jié)構(gòu)長(zhǎng)度為8byte的最小整數(shù)倍,即取24byte其余均補(bǔ)0.
于是該結(jié)構(gòu)體的對(duì)齊參數(shù)就是8byte。
2)當(dāng)對(duì)齊參數(shù)n設(shè)置為16byte時(shí),結(jié)果同上,不再分析
3)當(dāng)對(duì)齊參數(shù)設(shè)置為4byte時(shí)
上例結(jié)果為:Len:20
1245036,1245040,1245048,1245052
內(nèi)存中成員分布如下:
Strua.c起始于一個(gè)4的整數(shù)倍的地址,接下來(lái)要在strua.c之后分配strua.d,由于strua.d長(zhǎng)度為8byte,大于對(duì)齊參數(shù)4byte,所以N=min(8,4)取最小的4字節(jié),所以向后找第一個(gè)能被4整除的地址來(lái)作為strua.d首地址,故取1245036+4,接著要在strua.d后分配strua.s,strua.s長(zhǎng)度為2byte小于4byte,取N=min(2,4)2byte對(duì)齊,由于strua.d后的地址為1245048可以被2
整除,所以直接在strua.d后面分配,strua.i的長(zhǎng)度為4byte,所以取N=min(4,4)4byte對(duì)齊,所以從strua.s向后找第一個(gè)能被4整除的位置即1245048+4來(lái)分配和strua.i,同時(shí)N的最大值為4byte,所以整個(gè)結(jié)構(gòu)的長(zhǎng)度為4byte的最小整數(shù)倍20byte
4)當(dāng)對(duì)齊參數(shù)設(shè)置為2byte時(shí)
上例結(jié)果為:Len:16
1245040,1245042,1245050,1245052
Strua.c分配后,向后找一第一個(gè)能被2整除的位置來(lái)存放strua.d,依次類(lèi)推
5)1byte對(duì)齊時(shí):
上例結(jié)果為:Len:15
1245040,1245041,1245049,1245051
此時(shí),N=min(sizeof(成員),1),取N=1,由于1可以整除任何整數(shù),所以各個(gè)成員依次分配,沒(méi)有間空.
6)當(dāng)結(jié)構(gòu)體成員為數(shù)組時(shí),并不是將整個(gè)數(shù)組當(dāng)成一個(gè)成員來(lái)對(duì)待,而是將數(shù)組的每個(gè)元素當(dāng)一個(gè)成員來(lái)分配,其他分配規(guī)則不變,如將上例的結(jié)構(gòu)體改為:
struct A
{
char c; //1byte
double d; //8byte
short s; //2byte
char szBuf[5];
};
對(duì)齊參數(shù)設(shè)置為8byte,則,運(yùn)行結(jié)果如下:
Len:24
1245032,1245040,1245048,1245050
Strua 的s分配后,接下來(lái)分配Strua 的數(shù)組szBuf[5],這里要單獨(dú)分配它的每個(gè)元素,由于是char類(lèi)型,所以N=min(1,8),取N=1,所以數(shù)組szBuf[5]的元素依次分配沒(méi)有間隙。
看完上述的例子,基本分配的規(guī)律和方法應(yīng)該已經(jīng)知道.下面主要說(shuō)明數(shù)組,嵌套結(jié)構(gòu)體,指針時(shí)的一些內(nèi)存對(duì)齊問(wèn)題.
最重要的是自己寫(xiě)程序證明.
2.數(shù)組,嵌套.
測(cè)試環(huán)境:64位 ubuntu;g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
#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:"<<(unsigned long)&arga.i -(unsigned long)&arga.jd<<endl;
cout<<"Argsa:"<<(unsigned long)&arga.jd-(unsigned long)&arga.test<<endl;
return 0;
}
輸出結(jié)果:
Args:32
10
Argsa:56
Argsa:12
Argsa:32
struct Args長(zhǎng)度32 struct Argsa長(zhǎng)度:56.
改成#pragma pack (16)結(jié)果一樣.
這個(gè)例子證明了三點(diǎn):
對(duì)齊長(zhǎng)度長(zhǎng)于struct中的類(lèi)型長(zhǎng)度最長(zhǎng)的值時(shí),設(shè)置的對(duì)齊長(zhǎng)度等于無(wú)用.
數(shù)組對(duì)齊的長(zhǎng)度是按照數(shù)組成員類(lèi)型長(zhǎng)度來(lái)比對(duì)的.
嵌套的結(jié)構(gòu)體中,所包含的結(jié)構(gòu)體的對(duì)齊長(zhǎng)度是結(jié)構(gòu)體的對(duì)齊長(zhǎng)度.
3.指針.主要是因?yàn)?2位和64位機(jī)尋址上
測(cè)試環(huán)境同2.(64位系統(tǒng))
#include <iostream>
#include <cstdio>
#pragma pack(4)
using namespace std;
struct Args
{
int i;
double d;
char *p;
char ch;
int *pi;
}args;
int main()
{
cout<<"args length:"<<sizeof(args)<<endl;
cout<<(unsigned long)&args.ch-(unsigned long)&args.p<<endl;
cout<<(unsigned long)&args.pi-(unsigned long)&args.ch<<endl;
return 0;
}
設(shè)置pack為4時(shí):
args length:32
8
4
設(shè)置pack為8時(shí):
args length:40
8
8
看了上述內(nèi)容,應(yīng)該能分析出來(lái)為什么是這個(gè)結(jié)果.這里不詳細(xì)描述.
3.不同編譯器中內(nèi)存對(duì)齊
VC 6.0上是8 byte
gcc 默認(rèn)是8byte.測(cè)試版本gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
g++默認(rèn)是8byte.測(cè)試版本g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
但查閱的資料說(shuō)是gcc 默認(rèn)是4,且不支持pragma參數(shù)設(shè)定.測(cè)試的時(shí)候gcc默認(rèn)對(duì)齊為8byte且,支持pragma參數(shù).
測(cè)試過(guò)兩個(gè)不同的例子,結(jié)果相同.
4.什么時(shí)候需要進(jìn)行內(nèi)存對(duì)齊.
一般情況下都不需要對(duì)編譯器進(jìn)行的內(nèi)存對(duì)齊規(guī)則進(jìn)行修改,因?yàn)檫@樣會(huì)降低程序的性能,除非在以下兩種情況下:
(1)這個(gè)結(jié)構(gòu)需要直接被寫(xiě)入文件;
(2)這個(gè)結(jié)構(gòu)需通過(guò)網(wǎng)絡(luò)傳給其他程序;
相關(guān)文章
C++利用jsoncpp庫(kù)實(shí)現(xiàn)寫(xiě)入和讀取json文件
JsonCpp 是一個(gè)C++庫(kù),允許操作 JSON 值,包括序列化和反序列化到字符串和從字符串反序列化。本文主要介紹了如何利用jsoncpp庫(kù)實(shí)現(xiàn)寫(xiě)入和讀取json文件,感興趣的可以了解一下2023-04-04c語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之并查集 總結(jié)
一種用于管理分組的數(shù)據(jù)結(jié)構(gòu)。它具備兩個(gè)操作:(1)查詢(xún)?cè)豠和元素b是否為同一組 (2) 將元素a和b合并為同一組,需要的朋友可以參考下2018-08-08C++中拷貝構(gòu)造函數(shù)的總結(jié)詳解
深拷貝和淺拷貝可以簡(jiǎn)單理解為:如果一個(gè)類(lèi)擁有資源,當(dāng)這個(gè)類(lèi)的對(duì)象發(fā)生復(fù)制過(guò)程的時(shí)候,資源重新分配,這個(gè)過(guò)程就是深拷貝,反之,沒(méi)有重新分配資源,就是淺拷貝2013-09-09C++實(shí)現(xiàn)LeetCode(40.組合之和之二)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(40.組合之和之二),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07VS2019 更新MSDN并創(chuàng)建快捷方式的實(shí)現(xiàn)
這篇文章主要介紹了VS2019 更新MSDN并創(chuàng)建快捷方式的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03C++利用宏實(shí)現(xiàn)類(lèi)成員反射詳解
這篇文章主要為大家詳細(xì)介紹了C++如何利用宏實(shí)現(xiàn)類(lèi)成員反射,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,有興趣的小伙伴可以了解一下2024-01-01C語(yǔ)言如何實(shí)現(xiàn)循環(huán)輸入
這篇文章主要介紹了C語(yǔ)言如何實(shí)現(xiàn)循環(huán)輸入問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02解析C語(yǔ)言中空指針、空指針常量、NULL & 0的詳解
本篇文章是對(duì)C語(yǔ)言中空指針、空指針常量、NULL & 0 進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05OpenCV + MFC實(shí)現(xiàn)簡(jiǎn)單人臉識(shí)別
這篇文章主要為大家詳細(xì)介紹了OpenCV + MFC實(shí)現(xiàn)簡(jiǎn)單人臉識(shí)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08