1. 程式人生 > >__declspec(align())記憶體對齊

__declspec(align())記憶體對齊

原文地址:http://www.cppblog.com/deercoder/archive/2011/03/13/141747.html

上面講到了關於pack的記憶體對齊和計算方法,這裡繼續講實現記憶體對齊的另一種方式:__declspec( align(#) )

__declspec( align(#) )和#pragma pack( n )有密切聯絡。

當一個變數或結構體同時受兩者影響時,前者的優先順序高。

成員的地址決定於前者及後者,其要麼是前者的倍數,要麼是後者的倍數,要麼是成員的大小的倍數,取最小。

結構體最後的大小於前者有關,其要麼是前者的倍數,要麼是結構體中最大偏移量的倍數,取最大。

要算出最後結果,必須知道兩者的值或預設值。

下面舉一個例子來詳細的分析:

#include <stdio.h>

#include 
"stdafx.h"
#include 
<stdlib.h>//using namespace std;
#pragma pack( push, 
4 )

__declspec( align(
32) )struct D
{
    
int i1;
    
double d1;
    
int i2;
    
int i3;
};

int main()
{
    cout 
<<"sizeof(int) = "<<sizeof(int<< endl;
    cout 
<<"sizeof(char) = "<<sizeof(char<< endl;
    cout 
<<"sizeof(double) = "<<sizeof(double<< endl;
    cout 
<<sizeof(D) << endl;
    system(
"PAUSE");
    
return0;
}

這段程式碼在VS 2010中的執行結果是,sizeof(D)的大小為32,而在Dev C++,C-Free 5.0以及gcc中的結果都似乎20。下面我們來著重講講關於__declspec( align(#) )的用法:

正如前面所說的,當有__declspec( align(#) )和pack的時候,__declspec( align(#) )的優先順序要高些。所以對於上面這個例子,我們首先來計算出來每一個的大小。

1.       成員的地址如何取?

規則:成員的地址要取pack(n),__declspec( align(m) ),以及成員自身大小這三者之間的最小值,也就是,min(n,m,sizeof(成員變數型別)),那麼我們可以對每一個結構體的成員都進行分析。

第一個為int型別,佔據4B,所以地址是[0~3].

第二個為double型別,它的地址要根據min(4,32,sizeof(double))來判斷,所以應該是4的倍數,也就是相鄰著int型別的i1存放。地址是[4~11]。

第三個為int型別,佔據4B,同樣應該是4的倍數,地址是[12~15].

第四個為int型別,佔據4B,地址為[16~19].

從而總的地址是從[0~19]連續存放的20個位元組,那麼是否sizeof(D)的大小就是20呢?

經過測試,我們可以看到,在VS 2010中,結果是32,why?

這就要用__declspec( align(#) )來解釋了。也就是下面第二點的內容。

2.       結構體最後的大小如何決定?

規則:結構體最後的大小與__declspec( align(m) )有關,其要麼是它的倍數,要麼是結構體中最大偏移量的倍數,取最大

根據這個規則,這裡align是32,而結構體中最大的是double型別,也就是應該是max(32,8)=32,所以最後結構體的大小應該是32的倍數,而明顯上面我們看到的實際大小是20B,從而需要擴充套件到32B。

在這裡,就體現了__declspec( align(m) )的強大作用!

同樣的,為了體現該語句的作用,我們去掉這個語句,運用我們前面一節內容的知識,來計算並測試sizeof(D),最終不論是在VS 2010還是Dev C++中,執行的結果都是上面我們所預測的20B。

OK,下面回到最後的疑問,也就是前面我們提出的,為何加入了__declspec( align(m) )語句之後,在DevC++和VS 2010的結果不同?

實際上,對於這些記憶體對齊的處理,不同的編譯器可能採取不同的處理,就像前面一節中所說的,我將pack誤用為package,導致根本沒有達到按照我要求的位元組對齊的目的,而且編譯器根本不提供任何警告資訊。那麼,這裡合理的解釋是:Dev C++不支援這種用法。

接下來我舉一個例子來說明SSE的指令函式是如何使用的,必須要說明的是我以下的程式碼都是在VC7.1的平臺上寫的,不保證對其它如Dev-C++Borland C++等開發平臺的完全相容。

這裡要注意一下,我使用了__declspec(align(16))做為陣列定義的修釋符,這表示該陣列是以16位元組為邊界對齊的,因為SSE指令只能支援這種格式的記憶體資料。

我們在這裡看到了SSE算的強大,相信它會成為多媒體程式設計師手中用來對付無窮盡流媒體資料的一把利劍。我後面還會寫一些關於SSE演算法更復雜應用的文章,敬請關注,感謝您抽時間閱讀!

從這篇文章我們可以看到,SSE指令集的情況下,在VC 7.1下才支援__declspec(align(16))這種用法,而對於其他平臺不一定有效。而前面我們使用的Dev C++以及C-Free,都是基於g++或者MinGW,不一定會支援這種方式,或者說,不一定按照這種記憶體對齊的建議來做,也就造成了結果的不同。

下面我們來繼續探討結構體中有結構體的情況。

先看看下面這段程式碼:

#include <stdio.h>

#include 
"stdafx.h"
#include 
<stdlib.h>//using namespace std;
#pragma pack( push, 
4 )

__declspec( align(
32) )struct D
{
    
int i1;
    
double d1;
    
int i2;
    
int i3;
};

__declspec( align(
16) ) struct E
{
     
int i1;
     D m_d;
     
int i2;
};

int main()
{
    cout 
<<"sizeof(int) = "<<sizeof(int<< endl;
    cout 
<<"sizeof(char) = "<<sizeof(char<< endl;
    cout 
<<"sizeof(double) = "<<sizeof(double<< endl;
    cout 
<<sizeof(D) << endl;
    cout 
<<sizeof(E) << endl;
    system(
"PAUSE");
    
return0;
}

最後執行的結果是sizeof(E)為96,為何會是這個結果呢?我們來詳細講解下。

對於結構體E,第一個元素為int型別,所以佔據[0~3]地址單元。

第二個元素是一個結構體,該結構體由於受上面__declspecalign(32) )的影響,優先順序高,所以起始地址是32的倍數,而且大小為32B,從而應該放置在[32~63]單元處。

最後一個是int型別的變數,大小為4,所以應該是4的倍數,地址為[64~67]。

故結構體E的大小應該是從[0~67],佔據68B,而由於前面還有限制__declspecalign(16) ),同時成員變數的最大偏移是sizeof(D)=32,所以我們最後這個結構體的大小應該是他們中最大值的倍數,也就是32的倍數,68向上取32的倍數應該是96.故結果為96.

最後仍然是上面平臺的問題,在Dev C++和G++下面的結果不同,原因上面解釋了。


MSDN:

“The sizeof value for any structure is the offset of the final member, plus that member's size, rounded up to the nearest multiple of the largest member alignment value or the whole structure alignment value, whichever is greater.”

中文:

“sizeof的結果都是結構體中最後的一個成員變數加上它的大小,再加上一個填充容量(padding),這個填充大小是成員變數最大的一個對齊引數或整個結構體的對齊引數的倍數,取哪個決定於哪個對齊引數較大”

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vclang/html/e4209cbb-5437-4b53-b3fe-ac264501d404.htm

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vclang/html/9cb63f58-658b-4425-ac47-af8eabfc5878.htm

P.S.:上面是關於記憶體對齊的研究,如有謬誤,歡迎指出!


附參考資料和拓展: