?? 談談對齊 - c-c++ - embeded linux.htm
字號:
short, int,
double等。復合類型由基本類型組成,例如結構。本文將基本類型的變量記作基本變量,將復合類型的變量記作復合變量或結構變量。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">基本變量的長度目前有1、2、4、8字節。以后可能會有更大的基本變量。嵌入式環境通常不支持浮點,常見的長度是1、2、4字節。</P>
<H4
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px">0.2.2
變量的地址</H4>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">從地址看,變量可以分成有確定地址的變量和沒有確定地址的變量。所謂“有確定地址”就是指在程序運行前就有確定的地址。而“沒有確定地址”的變量,它們的地址是在運行時確定的。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">全局變量和靜態變量都有確定地址。局部變量和動態分配的變量沒有確定地址。本文將有確定地址的變量記作有址變量。</P>
<H2
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px">1
變量對齊</H2>
<H3
style="BORDER-RIGHT: rgb(204,204,204) 1px dotted; PADDING-RIGHT: 0.6em; BORDER-TOP: rgb(204,204,204) 1px dotted; PADDING-LEFT: 0.6em; FONT-SIZE: 12px; PADDING-BOTTOM: 0.6em; MARGIN: 6px 0px 0px; BORDER-LEFT: rgb(204,204,204) 1px dotted; PADDING-TOP: 0.6em; BORDER-BOTTOM: rgb(204,204,204) 1px dotted; FONT-FAMILY: 'Microsoft yahei', verdana, sans-serif">1.1
沒有確定地址的變量</H3>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">局部變量是從堆棧分配的,編譯器通常會保證每個局部變量的地址都在4n邊界上。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">動態分配的變量是從堆上分配。堆的實現與標準庫和操作系統有關。在一些簡單的嵌入式系統中,我們需要自己實現動態內存分配,這時我們要保證每次分配的內存塊地址都在4n邊界上,以避免后面談到的數據對齊問題。</P>
<H3
style="BORDER-RIGHT: rgb(204,204,204) 1px dotted; PADDING-RIGHT: 0.6em; BORDER-TOP: rgb(204,204,204) 1px dotted; PADDING-LEFT: 0.6em; FONT-SIZE: 12px; PADDING-BOTTOM: 0.6em; MARGIN: 6px 0px 0px; BORDER-LEFT: rgb(204,204,204) 1px dotted; PADDING-TOP: 0.6em; BORDER-BOTTOM: rgb(204,204,204) 1px dotted; FONT-FAMILY: 'Microsoft yahei', verdana, sans-serif">1.2
有確定地址的變量</H3>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">有址變量的地址是在鏈接時確定的。編譯器通常有設置變量對齊方式的編譯選項,我們通常使用該選項的默認值。在默認情況下,編譯器會按照默認方式對齊放置有址變量。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">所謂按“按默認方式對齊”,就是將長度為1的基本變量放在1n邊界上。將長度為2的基本變量放在2n邊界上。將長度為4的基本變量放在4n邊界上,依此類推。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">每個結構變量總是由一個個基本變量構成。結構變量按照該結構中最長的基本變量對齊。如果某個結構基本變量的最大長度是1,編譯器就可以把這個結構放在1n邊界上。如果某個結構基本變量的最大長度是4,編譯器就應該把這個結構放在4n邊界上。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">那么結構中的成員變量又是怎樣對齊的?</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px"><SPAN
class=Apple-style-span style="FONT-SIZE: 14px"><SPAN
class=Apple-style-span
style="FONT-SIZE: 12px; LINE-HEIGHT: 18px">
<H3
style="BORDER-RIGHT: rgb(204,204,204) 1px dotted; PADDING-RIGHT: 0.6em; BORDER-TOP: rgb(204,204,204) 1px dotted; PADDING-LEFT: 0.6em; FONT-SIZE: 12px; PADDING-BOTTOM: 0.6em; MARGIN: 6px 0px 0px; BORDER-LEFT: rgb(204,204,204) 1px dotted; PADDING-TOP: 0.6em; BORDER-BOTTOM: rgb(204,204,204) 1px dotted; FONT-FAMILY: 'Microsoft yahei', verdana, sans-serif">1.3
變量對齊會帶來什么麻煩?</H3>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">我在變量對齊問題上吃過一次虧,可以作為本節的一個例子。不過要理解這個例子,讀者必須知道ARM
CPU的一個特點:就是長度為m的基本變量必須放在mn邊界上,否則讀寫時會發生數據訪問錯誤,其中m=2或4。這就是第3節要介紹的數據對齊。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">事情是這樣,我定義了幾個緩沖區(大數組),然后動態分配這些內存。我的錯誤在于將這些數組定義為字節數組。我的分配算法是按塊分配,每個數據塊的大小都是4的整數倍。讀者能猜到錯誤產生的原因了嗎?</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">由于我把緩沖區定義為字節數組,編譯器就可以把它們放在1n邊界。如果緩沖區的起始地址是奇數地址,從緩沖區分配的內存塊的起始地址都是奇數地址。如果這些內存塊被用于需要按2或4字節對齊的變量,讀寫時就會發生數據訪問錯誤。如果編譯器恰好把這些緩沖區放在4n邊界上,問題就不會暴露出來。所以前一次編譯可能是好的,但是下一次編譯就會發生莫名其妙的錯誤。調試程序與偵破案件差不多,離犯罪現場越遠的兇手就越難發現。在我透過各種表象找到根源之前,吃點苦頭是難免的。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">解決問題的方法很簡單,將緩沖區定義為unsigned
int(下文記作uint32)的數組,編譯器自然會把它們放到4n邊界。在嵌入式系統中,我們經常要為任務定義堆棧。這些堆棧通常都是uint32類型的數組。你知道為什么要把它們定義成uint32數組,而不能定義成字節數組了嗎?</P>
<H2
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px">2
結構對齊</H2>
<H3
style="BORDER-RIGHT: rgb(204,204,204) 1px dotted; PADDING-RIGHT: 0.6em; BORDER-TOP: rgb(204,204,204) 1px dotted; PADDING-LEFT: 0.6em; FONT-SIZE: 12px; PADDING-BOTTOM: 0.6em; MARGIN: 6px 0px 0px; BORDER-LEFT: rgb(204,204,204) 1px dotted; PADDING-TOP: 0.6em; BORDER-BOTTOM: rgb(204,204,204) 1px dotted; FONT-FAMILY: 'Microsoft yahei', verdana, sans-serif">2.1
基本長度</H3>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">為了描述方便,我們定義一個基本長度的概念。一個基本變量的基本長度就是它的長度,一個結構變量的基本長度就是結構成員中基本變量的最大長度。前面說過:在默認情況下,結構變量就是按照其基本長度對齊的。</P>
<H3
style="BORDER-RIGHT: rgb(204,204,204) 1px dotted; PADDING-RIGHT: 0.6em; BORDER-TOP: rgb(204,204,204) 1px dotted; PADDING-LEFT: 0.6em; FONT-SIZE: 12px; PADDING-BOTTOM: 0.6em; MARGIN: 6px 0px 0px; BORDER-LEFT: rgb(204,204,204) 1px dotted; PADDING-TOP: 0.6em; BORDER-BOTTOM: rgb(204,204,204) 1px dotted; FONT-FAMILY: 'Microsoft yahei', verdana, sans-serif">2.2
對齊</H3>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">在默認情況下,可以認為結構的成員按照默認方式對齊,即長度為m的基本變量放在mn邊界上,其中m=1,2,4或8。因為要把成員對齊,結構的各成員間就可能出現填充字節,結構的大小可能大于各成員大小之和。例如:</P><PRE>typedef struct St1Tag
{
char ch1;
int num1;
short sh1;
short sh2;
char ch2;
} St1;
</PRE>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">這個結構的基本長度是4,所以這個結構的變量要放在4n邊界。成員num1的基本長度為4,所以也要放在4n邊界。成員ch1從4n邊界開始,只占1個字節,所以在ch1和num1之間有3個填充字節。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">在對齊時,編譯器會將結構長度取整到基本長度的整數倍。這樣以該結構為基本類型的數組既可以連續排列,每個元素又可以對齊放置。所以,sizeof(St1)的值是16,在St1的最后一個成員ch2后面還有3個填充字節。</P>
<H3
style="BORDER-RIGHT: rgb(204,204,204) 1px dotted; PADDING-RIGHT: 0.6em; BORDER-TOP: rgb(204,204,204) 1px dotted; PADDING-LEFT: 0.6em; FONT-SIZE: 12px; PADDING-BOTTOM: 0.6em; MARGIN: 6px 0px 0px; BORDER-LEFT: rgb(204,204,204) 1px dotted; PADDING-TOP: 0.6em; BORDER-BOTTOM: rgb(204,204,204) 1px dotted; FONT-FAMILY: 'Microsoft yahei', verdana, sans-serif">2.3
緊縮</H3>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">各編譯器都支持結構的緊縮,即連續排列結構的各成員變量,各成員變量之間沒有任何填充字節。這時,結構的大小等于各成員變量大小的和。緊縮結構的變量可以放在1n邊界,即任意地址邊界。</P>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">在gcc中可以這樣定義緊縮結構:</P><PRE>typedef struct St2Tag
{
St1 st1;
char ch2;
}
__attribute__ ((packed)) St2;
</PRE>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">armcc是這樣的:</P><PRE>typedef __packed struct St2Tag
{
St1 st1;
char ch2;
} St2;
</PRE>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">VC的寫法最麻煩:</P><PRE>#pragma pack(1)
typedef struct St2Tag
{
St1 st1;
char ch2;
} St2;
#pragma pack()
</PRE>
<P
style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; FONT-SIZE: 14px; PADDING-BOTTOM: 0px; MARGIN: 1em 0px 0.5em; TEXT-INDENT: 2em; LINE-HEIGHT: 22px; PADDING-TOP: 0px">如果要同時支持gcc、armcc、VC平臺,可以把代碼寫成這樣:</P><PRE>#ifdef __GNUC__
#define GNUC_PACKED __attribute__ ((packed))
#else
#define GNUC_PACKED
#endif
#ifdef __arm
#define ARM_PACKED __packed
#else
#define ARM_PACKED
#endif
#ifdef WIN32
#pragma pack(1)
#endif
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -