轉載鏈接1:http://blog.csdn.net/21aspnet/article/details/6729724html
轉載鏈接2:http://blog.chinaunix.net/uid-10995602-id-2918694.htmlc++
文章最後本人作了一幅圖,一看就明白了,這個問題網上講的很多,可是都沒有把問題說透。程序員
1、概念 web
對齊跟數據在內存中的位置有關。若是一個變量的內存地址正好位於它長度的整數倍,他就被稱作天然對齊。好比在32位cpu下,假設一個整型變量的地址爲0x00000004,那它就是天然對齊的。編程
2、爲何要字節對齊 數組
須要字節對齊的根本緣由在於CPU訪問數據的效率問題。假設上面整型變量的地址不是天然對齊,好比爲0x00000002,則CPU若是取它的值的話須要訪問兩次內存,第一次取從0x00000002-0x00000003的一個short,第二次取從0x00000004-0x00000005的一個short而後組合獲得所要的數據,若是變量在0x00000003地址上的話則要訪問三次內存,第一次爲char,第二次爲short,第三次爲char,而後組合獲得整型數據。而若是變量在天然對齊位置上,則只要一次就能夠取出數據。一些系統對對齊要求很是嚴格,好比sparc系統,若是取未對齊的數據會發生錯誤,舉個例: 緩存
// 數據結構
char ch[8]; 多線程
char *p = &ch[1]; 架構
int i = *(int *)p;
......
運行時會報segment error,而在x86上就不會出現錯誤,只是效率降低。
3、正確處理字節對齊
對於標準數據類型,它的地址只要是它的長度的整數倍就好了,而非標準數據類型按下面的原則對齊:
數組 :按照基本數據類型對齊,第一個對齊了後面的天然也就對齊了。
聯合 :按其包含的長度最大的數據類型對齊。
結構體: 結構體中每一個數據類型都要對齊。
好比有以下一個結構體:
struct stu {
char sex;
int length;
char name[10];
};
struct stu my_stu;
因爲在x86下,GCC默認按4字節對齊,它會在sex後面跟name後面分別填充三個和兩個字節使length和整個結構體對齊。因而咱們sizeof(my_stu)會獲得長度爲20,而不是15.
4、__attribute__選項
咱們能夠按照本身設定的對齊大小來編譯程序,GNU使用__attribute__選項來設置,好比咱們想讓剛纔的結構按一字節對齊,咱們能夠這樣定義結構體
struct stu{
char sex;
int length;
char name[10];
}__attribute__ ((aligned (1)));
struct stu my_stu;
則sizeof(my_stu)能夠獲得大小爲15。
上面的定義等同於
struct stu{
char sex;
int length;
char name[10];
}__attribute__ ((packed));
struct stu my_stu;
__attribute__((packed))得變量或者結構體成員使用最小的對齊方式,即對變量是一字節對齊,對域(field)是位對齊.
5、何時須要設置對齊
在設計不一樣CPU下的通訊協議時,或者編寫硬件驅動程序時寄存器的結構這兩個地方都須要按一字節對齊。即便看起來原本就天然對齊的也要使其對齊,以避免不一樣的編譯器生成的代碼不同.
//************************************************************************************************************************************************************//
//************************************************************************************************************************************************************//
//************************************************************************************************************************************************************//
1、快速理解
1. 什麼是字節對齊?
在C語言中,結構是一種複合數據類型,其構成元素既能夠是基本數據類型(如int、long、float等)的變量,也能夠是一些複合數據類型(如數組、結構、聯合等)的數據單元。在結構中,編譯器爲結構的每一個成員按其天然邊界(alignment)分配空間。各個成員按照它們被聲明的順序在內存中順序存儲,第一個成員的地址和整個結構的地址相同。爲了使CPU可以對變量進行快速的訪問,變量的起始地址應該具備某些特性,即所謂的」對齊」. 好比4字節的int型,其起始地址應該位於4字節的邊界上,即起始地址可以被4整除.
2. 字節對齊有什麼做用?
字節對齊的做用不只是便於cpu快速訪問,同時合理的利用字節對齊能夠有效地節省存儲空間。
對於32位機來講,4字節對齊可以使cpu訪問速度提升,好比說一個long類型的變量,若是跨越了4字節邊界存儲,那麼cpu要讀取兩次,這樣效率就低了。可是在32位機中使用1字節或者2字節對齊,反而會使變量訪問速度下降。因此這要考慮處理器類型,另外還得考慮編譯器的類型。在vc中默認是4字節對齊的,GNU gcc 也是默認4字節對齊。
3. 更改C編譯器的缺省字節對齊方式
在缺省狀況下,C編譯器爲每個變量或是數據單元按其天然對界條件分配空間。通常地,能夠經過下面的方法來改變缺省的對界條件: · 使用僞指令#pragma pack (n),C編譯器將按照n個字節對齊。 · 使用僞指令#pragma pack (),取消自定義字節對齊方式。
另外,還有以下的一種方式: · __attribute((aligned (n))),讓所做用的結構成員對齊在n字節天然邊界上。若是結構中有成員的長度大於n,則按照最大成員的長度來對齊。 · __attribute__ ((packed)),取消結構在編譯過程當中的優化對齊,按照實際佔用字節數進行對齊。
4. 舉例說明
例1
struct test {
char x1;
short x2;
float x3;
char x4;
};
因爲編譯器默認狀況下會對這個struct做天然邊界(有人說「天然對界」我以爲邊界更順口)對齊,結構的第一個成員x1,其偏移地址爲0,佔據了第1個字節。第二個成員x2爲short類型,其起始地址必須2字節對界,所以,編譯器在x2和x1之間填充了一個空字節。結構的第三個成員x3和第四個成員x4剛好落在其天然邊界地址上,在它們前面不須要額外的填充字節。在test結構中,成員x3要求4字節對界,是該結構全部成員中要求的最大邊界單元,於是test結構的天然對界條件爲4字節,編譯器在成員x4後面填充了3個空字節。整個結構所佔據空間爲12字節。
例2
#pragma pack(1) //讓編譯器對這個結構做1字節對齊
struct test {
char x1;
short x2;
float x3;
char x4;
};
#pragma pack() //取消1字節對齊,恢復爲默認4字節對齊
這時候sizeof(struct test)的值爲8。
例3
#define GNUC_PACKED __attribute__((packed))
struct PACKED test {
char x1;
short x2;
float x3;
char x4;
}GNUC_PACKED;
這時候sizeof(struct test)的值仍爲8。
2、深刻理解
什麼是字節對齊,爲何要對齊? TragicJun 發表於 2006-9-18 9:41:00 現代計算機中內存空間都是按照byte劃分的,從理論上講彷佛對任何類型的變量的訪問能夠從任何地址開始,但實際狀況是在訪問特定類型變量的時候常常在特定的內存地址訪問,這就須要各類類型數據按照必定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
1.對齊的做用和緣由:
各個硬件平臺對存儲空間的處理上有很大的不一樣。一些平臺對某些特定類型的數據只能從某些特定地址開始存取。好比有些架構的CPU在訪問一個沒有進行對齊的變量的時候會發生錯誤,那麼在這種架構下編程必須保證字節對齊.其餘平臺可能沒有這種狀況,可是最多見的是若是不按照適合其平臺要求對數據存放進行對齊,會在存取效率上帶來損失。好比有些平臺每次讀都是從偶地址開始,若是一個int型(假設爲32位系統)若是存放在偶地址開始的地方,那麼一個讀週期就能夠讀出這32bit,而若是存放在奇地址開始的地方,就須要2個讀週期,並對兩次讀出的結果的高低字節進行拼湊才能獲得該32bit數據。顯然在讀取效率上降低不少。
2.字節對齊對程序的影響:
先讓咱們看幾個例子吧(32bit,x86環境,gcc編譯器):
設結構體以下定義:
struct A {
int a;
char b;
short c;
};
struct B {
char b;
int a;
short c;
};
如今已知32位機器上各類數據類型的長度以下:
char:1(有符號無符號同)
short:2(有符號無符號同)
int:4(有符號無符號同)
long:4(有符號無符號同)
float:4
double:8 那麼上面兩個結構大小如何呢?
結果是: sizeof(strcut A)值爲8 sizeof(struct B)的值倒是12
結構體A中包含了4字節長度的int一個,1字節長度的char一個和2字節長度的short型數據一個,B也同樣;按理說A,B大小應該都是7字節。 之因此出現上面的結果是由於編譯器要對數據成員在空間上進行對齊。上面是按照編譯器的默認設置進行對齊的結果,那麼咱們是否是能夠改變編譯器的這種默認對齊設置呢,固然能夠.例如:
#pragma pack (2) /*指定按2字節對齊*/
struct C {
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
sizeof(struct C)值是8。
修改對齊值爲1:
#pragma pack (1) /*指定按1字節對齊*/
struct D {
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
sizeof(struct D)值爲7。 後面咱們再講解#pragma pack()的做用.
三.編譯器是按照什麼樣的原則進行對齊的?
先讓咱們看四個重要的基本概念:
1.數據類型自身的對齊值:對於char型數據,其自身對齊值爲1,對於short型爲2,對於int,float,double類型,其自身對齊值爲4,單位字節。
2.結構體或者類的自身對齊值:其成員中自身對齊值最大的那個值。
3.指定對齊值:#pragma pack (value)時的指定對齊值value。
4.數據成員、結構體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。
有了這些值,咱們就能夠很方便的來討論具體數據結構的成員和其自身的對齊方式。有效對齊值N是最終用來決定數據存放地址方式的值,最重要。有效對齊N,就是表示「對齊在N上」,也就是說該數據的"存放起始地址%N=0".而數據結構中的數據變量都是按定義的前後順序來排放的。第一個數據變量的起始地址就是數據結構的起始地址。結構體的成員變量要對齊排放,結構體自己也要根據自身的有效對齊值圓整(就是結構體成員變量佔用總長度須要是對結構體有效對齊值的整數倍,結合下面例子理解)。這樣就不能理解上面的幾個例子的值了。
例子分析: 分析例子B;
struct B {
char b;
int a;
short c;
};
假設B從地址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環境下,該值默認爲4。
第一個成員變量b的自身對齊值是1,比指定或者默認指定對齊值4小,因此其有效對齊值爲1,因此其存放地址0x0000符合0x0000%1=0.
第二個成員變量a,其自身對齊值爲4,因此有效對齊值也爲4,因此只能存放在起始地址爲0x0004到0x0007這四個連續的字節空間中,複覈0x0004%4=0,且緊靠第一個變量。第三個變量c,自身對齊值爲2,因此有效對齊值也是2,能夠存放在0x0008到0x0009這兩個字節空間中,符合0x0008%2=0。因此從0x0000到0x0009存放的都是B內容。再看數據結構B的自身對齊值爲其變量中最大對齊值(這裏是b)因此就是4,因此結構體的有效對齊值也是4。
根據結構體圓整的要求,0x0009到0x0000=10字節,(10+2)%4=0。因此0x0000A到0x000B也爲結構體B所佔用。故B從0x0000到0x000B共有12個字節,sizeof(struct B)=12;其實若是就這一個就來講它已將知足字節對齊了,由於它的起始地址是0,所以確定是對齊的,之因此在後面補充2個字節,是由於編譯器爲了實現結構數組的存取效率,試想若是咱們定義了一個結構B的數組,那麼第一個結構起始地址是0沒有問題,可是第二個結構呢?按照數組的定義,數組中全部元素都是緊挨着的,若是咱們不把結構的大小補充爲4的整數倍,那麼下一個結構的起始地址將是0x0000A,這顯然不能知足結構的地址對齊了,所以咱們要把結構補充成有效對齊大小的整數倍.其實諸如:對於char型數據,其自身對齊值爲1,對於short型爲2,對於int,float,double類型,其自身對齊值爲4,這些已有類型的自身對齊值也是基於數組考慮的,只是由於這些類型的長度已知了,因此他們的自身對齊值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字節對齊*/
struct C {
char b;
int a;
short c;
};
#pragma pack () /*取消指定對齊,恢復缺省對齊*/
第一個變量b的自身對齊值爲1,指定對齊值爲2,因此,其有效對齊值爲1,假設C從0x0000開始,那麼b存放在0x0000,符合0x0000%1=0;
第二個變量,自身對齊值爲4,指定對齊值爲2,因此有效對齊值爲2,因此順序存放在0x000二、0x000三、0x000四、0x0005四個連續字節中,符合0x0002%2=0。
第三個變量c的自身對齊值爲2,因此有效對齊值爲2,順序存放 在0x000六、0x0007中,符合0x0006%2=0。因此從0x0000到0x00007共八字節存放的是C的變量。又C的自身對齊值爲4,因此C的有效對齊值爲2。又8%2=0,C只佔用0x0000到0x0007的八個字節。因此sizeof(struct C)=8.
四.如何修改編譯器的默認對齊值?
1.在VC IDE中,能夠這樣修改:[Project]|[Settings],c/c++選項卡Category的Code Generation選項的Struct Member Alignment中修改,默認是8字節。
2.在編碼時,能夠這樣動態修改:#pragma pack .注意:是pragma而不是progma.
五.針對字節對齊,咱們在編程中如何考慮?
若是在編程的時候要考慮節約空間的話,那麼咱們只須要假定結構的首地址是0,而後各個變量按照上面的原則進行排列便可,基本的原則就是把結構中的變量按照類型大小從小到大聲明,儘可能減小中間的填補空間.還有一種就是爲了以空間換取時間的效率,咱們顯示的進行填補空間進行對齊,好比:有一種使用空間換時間作法是顯式的插入reserved成員:
struct A{
char a;
char reserved[3];//使用空間換時間
int b;
}
reserved成員對咱們的程序沒有什麼意義,它只是起到填補空間以達到字節對齊的目的,固然即便不加這個成員一般編譯器也會給咱們自動填補對齊,咱們本身加上它只是起到顯式的提醒做用.
六.字節對齊可能帶來的隱患:
代碼中關於對齊的隱患,不少是隱式的。好比在強制類型轉換的時候。例如:
unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;
p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
最後兩句代碼,從奇數邊界去訪問unsignedshort型變量,顯然不符合對齊的規定。 在x86上,相似的操做只會影響效率,可是在MIPS或者sparc上,可能就是一個error,由於它們要求必須字節對齊.
七.如何查找與字節對齊方面的問題:
若是出現對齊或者賦值問題首先查看 1. 編譯器的big little端設置 2. 看這種體系自己是否支持非對齊訪問 3. 若是支持看設置了對齊與否,若是沒有則看訪問時須要加某些特殊的修飾來標誌其特殊訪問操做
舉例:
輸出:
8
12
8
7
修改代碼:
struct A {
// int a;
char b;
short c;
};
struct B {
char b;
// int a;
short c;
};
輸出:
4
4
輸出都是4,說明以前的int影響對齊!
看圖就明白了
//************************************************************************************************************************************************************//
//************************************************************************************************************************************************************//
//************************************************************************************************************************************************************//
在最近的項目中,咱們涉及到了「內存對齊」技術。對於大部分程序員來講,「內存對齊」對他們來講都應該是「透明的」。「內存對齊」應該是編譯器的 「管轄範圍」。編譯器爲程序中的每一個「數據單元」安排在適當的位置上。可是C語言的一個特色就是太靈活,太強大,它容許你干預「內存對齊」。若是你想了解 更加底層的祕密,「內存對齊」對你就不該該再透明瞭。
1、內存對齊的緣由
大部分的參考資料都是如是說的:
一、平臺緣由(移植緣由):不是全部的硬件平臺都能訪問任意地址上的任意數據 的;某些硬件平臺只能在某些地址處取某些特定類型的數據,不然拋出硬件異常。
二、性能緣由:數據結構(尤爲是棧)應該儘量地在天然邊界上對齊。 緣由在於,爲了訪問未對齊的內存,處理器須要做兩次內存訪問;而對齊的內存訪問僅須要一次訪問。
2、對齊規則
每一個特定平臺上的編譯器都有本身的默認「對齊係數」(也叫對齊模數)。程序員能夠經過預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一系數,其中的n就是你要指定的「對齊係數」。
規則:
一、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset爲0的地方,之後 每一個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
二、結構(或聯合)的總體對齊規則:在 數據成員完成各自對齊以後,結構(或聯合)自己也要進行對齊,對齊將按照#pragma pack指定的數值和結構(或聯合)最大數據成員長度中,比較小的那個進行。
三、結合一、2顆推斷:當#pragma pack的n值等於或超過全部數據成員長度的時候,這個n值的大小將不產生任何效果。
3、試驗
咱們經過一系列例子的詳細說明來證實這個規則吧!
我試驗用的編譯器包括GCC 3.4.2和VC6.0的C編譯器,平臺爲Windows XP + Sp2。
咱們將用典型的struct對齊來講明。首先咱們定義一個struct:
#pragma pack(n) /* n = 1, 2, 4, 8, 16 */
struct test_t {
int a;
char b;
short c;
char d;
};
#pragma pack(n)
首先咱們首先確認在試驗平臺上的各個類型的size,經驗證兩個編譯器的輸出均爲:
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
咱們的試驗過程以下:經過#pragma pack(n)改變「對齊係數」,而後察看sizeof(struct test_t)的值。
一、1字節對齊(#pragma pack(1))
輸出結果:sizeof(struct test_t) = 8 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(1)
struct test_t {
int a; /* 長度4 < 1 按1對齊;起始offset=0 0%1=0;存放位置區間[0,3] */
char b; /* 長度1 = 1 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 > 1 按1對齊;起始offset=5 5%1=0;存放位置區間[5,6] */
char d; /* 長度1 = 1 按1對齊;起始offset=7 7%1=0;存放位置區間[7] */
};
#pragma pack()
成員總大小=8
2) 總體對齊
總體對齊係數 = min((max(int,short,char), 1) = 1
總體大小 (size)=$(成員總大小) 按 $(總體對齊係數) 圓整 = 8 /* 8%1=0 */ [注1]
二、2字節對齊(#pragma pack(2))
輸出結果:sizeof(struct test_t) = 10 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(2)
struct test_t {
int a; /* 長度4 > 2 按2對齊;起始offset=0 0%2=0;存放位置區間[0,3] */
char b; /* 長度1 < 2 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 = 2 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d; /* 長度1 < 2 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9
2) 總體對齊
總體對齊係數 = min((max(int,short,char), 2) = 2
總體大小 (size)=$(成員總大小) 按 $(總體對齊係數) 圓整 = 10 /* 10%2=0 */
三、4字節對齊(#pragma pack(4))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(4)
struct test_t {
int a; /* 長度4 = 4 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* 長度1 < 4 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 < 4 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d; /* 長度1 < 4 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9
2) 總體對齊
總體對齊係數 = min((max(int,short,char), 4) = 4
總體大小 (size)=$(成員總大小) 按 $(總體對齊係數) 圓整 = 12 /* 12%4=0 */
四、8字節對齊(#pragma pack(8))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(8)
struct test_t {
int a; /* 長度4 < 8 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* 長度1 < 8 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 < 8 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d; /* 長度1 < 8 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9
2) 總體對齊
總體對齊係數 = min((max(int,short,char), 8) = 4
總體大小 (size)=$(成員總大小) 按 $(總體對齊係數) 圓整 = 12 /* 12%4=0 */
五、16字節對齊(#pragma pack(16))
輸出結果:sizeof(struct test_t) = 12 [兩個編譯器輸出一致]
分析過程:
1) 成員數據對齊
#pragma pack(16)
struct test_t {
int a; /* 長度4 < 16 按4對齊;起始offset=0 0%4=0;存放位置區間[0,3] */
char b; /* 長度1 < 16 按1對齊;起始offset=4 4%1=0;存放位置區間[4] */
short c; /* 長度2 < 16 按2對齊;起始offset=6 6%2=0;存放位置區間[6,7] */
char d; /* 長度1 < 16 按1對齊;起始offset=8 8%1=0;存放位置區間[8] */
};
#pragma pack()
成員總大小=9
2) 總體對齊
總體對齊係數 = min((max(int,short,char), 16) = 4
總體大小 (size)=$(成員總大小) 按 $(總體對齊係數) 圓整 = 12 /* 12%4=0 */
4、結論
8字節和16字節對齊試驗證實了「規則」的第3點:「當#pragma pack的n值等於或超過全部數據成員長度的時候,這個n值的大小將不產生任何效果」。另外內存對齊是個很複雜的東西,上面所說的在有些時候也可能不正 確。呵呵^_^
[注1]
什麼是「圓整」?
舉例說明:如上面的8字節對齊中的「總體對齊」,總體大小=9 按 4 圓整 = 12
圓整的過 程:從9開始每次加一,看是否能被4整除,這裏9,10,11均不能被4整除,到12時能夠,則圓整結束。
1 const
c onst限定的對象表示編譯器能夠將它放在只讀存儲器中,也就意味着在對其進行初始化以後就不能改變它的值。根據const使用的不一樣場合,大體能夠分爲三種狀況,其一限定普通變量,其二限定函數參數,其三限定指針變量。
第一和第二種狀況最爲簡單,語句①和語句②分別展現了它的用法。語句①定義了一個值爲10的整型常量。語句②中的const表示在函數體中不能 修改src指向的區域中的數據,這與函數的拷貝功能相對應,只作它應該作的事情而不該該有其餘反作用,編譯器能夠利用這些信息進行適當的優化。
① const int i=10;
② void *memcpy(void * dst, const void * src, size_t size);
③ const int *ptr;
④ int const *ptr;
⑤ int * const ptr;
⑥ int const * const ptr;
第3種狀況最爲複雜,雖然只是const位置不一樣,可是卻可能具備徹底不一樣的意義。通常,一個聲明語句由聲明說明符 (declspecifier)和一系列聲明子(declarator)兩部分組成,並且聲明說明符中的符號能夠以任何次序出現。理解聲明的第一步是定位 說明符和聲明子的邊界。這很容易:全部的說明符都是關鍵字或者類型名,所以說明符終止於第一個不是以上類型之一的符號。例如,在語句③和④中第一個既不是 關鍵字也不是類型名的符號是「*」,即聲明說明符分別爲const int和int const,因爲聲明說明符中的符號能夠以任意次序出現,所以語句③和④的含義是相同的。
爲了迅速弄清語句表達的含義,參考文獻[1]介紹了一種簡便的方法,其要點就是「逆序讀出定義」。
2 static與extern
static的含義隨着出現位置(全局變量仍是局部變量)和修飾對象(變量仍是函數)的不一樣而有很大的差異。下面各條目中的模塊指的是一個源文件或者一個翻譯單元:
① 位於函數體中的靜態變量在屢次函數調用間會維持其值。
② 位於模塊內(但在函數體外)的靜態變量能夠被模塊內的全部函數訪問,但不能被模塊外其餘函數訪問。也就是說,它是一個本地的全局變量。
③ 位於模塊內的靜態函數只能被此模塊內的其餘函數調用。也就是說,這個函數的做用域爲聲明所在的模塊。
static int global;/*狀況2*/
static void foo(void)/*狀況3*/
{
static int local = 0;/*狀況1*/
}
爲了清楚地理解static的3種用法,必須首先了解C語言中每一個標識符都具備的做用域、連接和存儲持續期等特性的含義。在ISO C99標準中,其定義以下:
① 對象的做用域指的是它僅在程序的某個區域中是可見的(便可以使用)。常見的做用域有文件做用域和塊做用域。
② 對象的存儲持續期決定對象的生命週期,即在程序執行某段區間中爲對象保留存儲區。有兩種類型的存儲持續期:靜態的和自動的。靜態存儲持續期的對象的生命週期爲程序執行的全過程,它的值在程序啓動前僅初始化一次。
③ 連接指的是在不一樣做用域中聲明的或者同一個做用域中屢次聲明的標識符能夠引用相同的對象或函數。有3種類型的連接:外部、內部和無。
在狀況②和③中,static分別用來修飾全局變量global和函數foo,改變它們的連接特性,使它們具備內部連接。也就是說,只有在定義它們的翻譯單元或者文件內才能使用它們,這對於建立模塊化的軟件很是重要。
與static相反,extern修飾的對象或函數具備外部連接。對於那些暴露給外部使用的接口函數應該使用extern限定,那些非接口函 數,例如工具函數或與實現細節相關的函數,則應該顯式地使用static限定。這是由於若是函數聲明不帶任何存儲類說明符,那麼它具備外部連接就好像使用 了extern同樣。
在狀況①中,static用來修飾局部變量local,將local的存儲持續期由自動的改變爲靜態的,這樣在foo函數的屢次調用間會爲其保 留值。注意做用域、連接和存儲持續期特性之間是正交的。例如在狀況①中,雖然變量local的存儲持續期變成靜態的,可是它的做用域仍然是塊做用域。
3 volatile
volatile關鍵字用來聲明這樣的對象,它們的值可能因爲程序控制以外的事件而被潛在改變。volatile強制編譯器不會對其所限定的對 象進行任何優化,每次讀寫都必須訪問實際的存儲器而不能使用寄存器中的副本。在實踐中,它大量的用來描述一個對應於內存映射的輸入/輸出端口,例如飛利浦 公司LPC21xx系列ARM處理器的向量地址寄存器定義爲:
#define VICVectAddr(*((volatile unsigned long *) 0xFFFFF030))
其次,中斷服務例程中使用的非自動變量或者多線程應用程序中多個任務共享的變量也必須使用volatile進行限定。例如在下面的示例中,若是 沒有使用volatile限定g_Flag變量,編譯器看到在foo函數中並無修改g_Flag,可能只執行一次g_Flag讀操做並將g_Flag的 值緩存在寄存器中,之後每次g_Flag讀操做都使用寄存器中的緩存值而不進行存儲器訪問,致使some_action函數永遠沒法執行。
int g_Flag = 0;
void foo(void){
while(1){
if(g_Flag){ some_action(); }
}
}
void isr_service(void){
g_Flag = 1;
}
4 __packed
在嵌入式軟件編程中,常常須要精確控制結構體在內存中的佈局和訪問非天然對齊的數據,可是C語言標準中並無統一的規定而是留給編譯器廠商自行 處理。在ARM C編譯器中,使用__packed關鍵字將任何類型的對齊設置爲1字節。在實踐中,__packed主要有兩個功能:其一,當它修飾指針時,表示此指針指 向的地址是非天然對齊的,編譯器會生成特殊的代碼以確保得到正確的結果;其二,當它修飾結構體、聯合或它們中的域時,能夠用來建立沒有填充的結構。
與其餘RISC架構同樣,ARM處理器可以高效地訪問對齊的數據,即字地址的末尾兩位爲零,半字地址的最後一位爲零,也稱這樣的數據位於它的自 然大小邊界或者是天然對齊的。ARM編譯器但願普通的「C」指針指向一個4字節對齊內存地址,這樣它能夠在代碼中使用LDR/STR指令一次操做4個字 節,不然只能使用LDRB/LDRH等字節/半字操做指令。相反若是指針指向一個非天然對齊的地址,例如若是一個整型指針指向地址0x8006,固然但願 裝載地址0x8006-0x8007-0x8008-0x8009處的數據,可是實際上ARM會對非天然對齊的地址進行轉換而從裝載地址 0x8004-0x8005-0x8006-0x8007處的數據。在下面的示例中(測試環境爲uVision3),首先定義了一個大小爲16字節的整型 數組,依次初始化爲0,1,2,…,15。因爲array是一個整型數組,編譯器會確保它是4字節對齊的,即指針pc指向一個4字節對齊的地址。運行程序 後,能夠看到若是對pc指針不加__packed標記進行修飾,將獲得一個奇怪的0x01000302;而在添加了__packed關鍵字以後,就獲得了 正確的結果。也就是說,若是要訪問非天然對齊的數據,必須使用__packed關鍵字顯式地標記出來。
int main() {
int i, j, array[4];
char *pc = (char *)array;
for(i=0; i<16; i++){
*(pc+i)=i;
}
/*確保pc指向一個4字節對齊的地址*/
while((int)pc & 0x3){ ++pc; }
/*訪問非天然對齊的整型數據,i=0x01000302*/
i = *((int*)(pc+2));
//訪問「標明」爲非天然對齊的數據 i=0x05040302
j = *((__packed int*)(pc+2));
}
ARM編譯器老是保證程序中的變量、結構體或聯合中的域分配到天然對齊的地址。這意味着編譯器常常須要在各個域之間插入填充,以確保每一個域的自 然對齊。一般來講,程序員能夠對這些填充視而不見,可是也有例外,例如爲了節省結構體佔用的空間,能夠利用__packed去除填充。在瞭解了編譯器的填 充行爲以後,能夠經過調整域的順序來減少結構體佔用的空間。例如雖然結構體s1和s2的域相同,可是sizeof(s1)等於16,而 sizeof(s2)等於12。
struct s1{ int i1; short s1; int i2; short s2; }; struct s2{ int i1; int i2; short s1; short s2; };