Content
→
→
◇#pragma once與 #ifndef
【參考】:http://blog.sina.com.cn/s/blog_4c5ad0740100ctq5.html
都用於避免同一個文件被include多次包含。在能夠支持這兩種方式的編譯器上,兩者的細微區別:
方式一:
#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__
... ... // 一些聲明語句
#endif<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
方式二:
#pragma once
... ... // 一些聲明語句
#ifndef方式保證同一個文件不會被包含多次,也能保證內容完全相同的兩個文件不會被不小心同時包含。缺點:就是若內容不同而頭文件宏明相同的兩個文件本同時包含時,可能就會導致頭文件明明存在,編譯器卻硬說找不到聲明的狀況
#pragma once則由編譯器提供保證:物理上的同一個文件不會被包含多次。注意不是指內容相同的兩個文件。帶來的好處是,你不必再費勁想個宏名了,也就不會出現宏名碰撞引發的問題。缺點:是如果某個頭文件有多份拷貝,則他們仍然可能被重複包含。當然,相比宏名碰撞引發的“找不到聲明”的問題,重複包含更容易被發現並修正。
#ifndef由語言支持所以移植性好,#pragma once可以避免名字衝突
附幾個重要的宏(MSDN):
>>_MSC_VER
>>Defines the compiler version. Defined as 1200 for Microsoft Visual C++ 6.0. >>Always defined.
The _MSC_VER macro will have one of the following values depending upon the particular Microsoft compiler:
Compiler _MSC_VER value
-------- --------------
C Compiler version 6.0 600
C/C++ compiler version 7.0 700
Visual C++, Windows, version 1.0 800
Visual C++, 32-bit, version 1.0 800
Visual C++, Windows, version 2.0 900
Visual C++, 32-bit, version 2.x 900
Visual C++, 32-bit, version 4.0 1000
Visual C++, 32-bit, version 5.0 1100
Visual C++, 32-bit, version 6.0 1200
>>_MFC_VER
>>Defines the MFC version. Defined as 0x0421 for Microsoft Foundation Class >>Library 4.21. Always defined.
>>#pragma once
>>Specifies that the file, in which the pragma resides, will be included (opened) >>only once by the compiler in a build. A common use for this pragma is the >>following:
>>//header.h
>>#pragma once
>>// Your C or C++ code would follow:
在所有的預處理指令中,#Pragma指令最複雜,作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。#pragma指令對每個編譯器給出了一個方法,在保持與C和C++語言完全兼容的情況下,給出主機或操作系統專有的特徵。依據定義,編譯的指示是機器或操作系統專有的,且對於每個編譯器都是不同的。
其格式一般爲: #Pragma Para
其中Para爲參數,下面來看一些常用的參數。
(1)message參數。Message參數能夠在編譯信息輸出窗口中輸出信息,其使用方法爲:
#Pragma message(“消息文本”)
當編譯器遇到這條指令時就在編譯輸出窗口中將消息文本打印出來。
當在程序中定義了許多宏來控制源代碼版本的時候,我們有可能會忘記有沒有正確的設置這些宏,此時我們可以用這條指令在編譯的時候就進行檢查。例如我們希望判斷自己有沒有在源代碼的什麼地方定義了_X86這個宏,可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 macro activated!”)
#endif
(2) code_seg參數。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
可以在程序中設置函數代碼存放的代碼段,當我們開發驅動程序的時候就會使用到它。
應用一:單應用程序。
有的時候我們可能想讓一個應用程序只啓動一次,就像單件模式(singleton)一樣,實現的方法可能有多種,這裏說說用#pragma data_seg來實現的方法,很是簡潔便利。
應用程序的入口文件前面加上
#pragma data_seg("flag_data")
int app_count = 0;
#pragma data_seg()
#pragma comment(linker,"/SECTION:flag_data,RWS")
然後程序啓動的地方加上
if(app_count>0) // 如果計數大於0,則退出應用程序。
{
//MessageBox(NULL, "已經啓動一個應用程序", "Warning", MB_OK);
//printf("no%d application", app_count);
return FALSE;
}
app_count++;
(3)#pragma once (比較常用)。
在頭文件最開始加入這條指令,能夠保證頭文件被編譯一次,這條指令實際上在VC6中就已經有了,但是考慮到兼容性並沒有太多的使用它。
(4)#pragma hdrstop。表示預編譯頭文件到此爲止,後面的頭文件不進行預編譯。BCB可以預編譯頭文件以加快鏈接的速度,但如果所有頭文件都進行預編譯又可能佔太多磁盤空間,所以使用這個選項排除一些頭文件。
有時單元之間有依賴關係,比如單元A依賴單元B,所以單元B要先於單元A編譯。你可以用#pragma startup指定編譯優先級,如果使用了#pragma package(smart_init) ,BCB就會根據優先級的大小先後編譯。
(5)#pragma resource "*.dfm"表示把*.dfm文件中的資源加入工程。*.dfm中包括窗體外觀的定義。
(6)#pragma warning(disable: 4507 34; once: 4385; error: 164)
等價於:
#pragma warning(disable:4507 34) // 不顯示4507和34號警告信息
#pragma warning(once:4385) // 4385號警告信息僅報告一次
#pragma warning(error:164) // 把164號警告信息作爲一個錯誤。
同時這個pragma warning 也支持如下格式:
#pragma warning(push [,n])
#pragma warning(pop)
這裏n代表一個警告等級(1---4)。
#pragma warning(push)保存所有警告信息的現有的警告狀態。
#pragma warning(push, n)保存所有警告信息的現有的警告狀態,並且把全局警告等級設定爲n。
#pragma warning(pop)向棧中彈出最後一個警告信息,在入棧和出棧之間所作的
一切改動取消。例如:
#pragma warning(push)
#pragma warning(disable: 4705)
#pragma warning(disable: 4706)
#pragma warning(disable: 4707)
//.......
#pragma warning(pop)
在這段代碼的最後,重新保存所有的警告信息(包括4705,4706和4707)。
(7)#pragma comment(...)
該指令將一個註釋記錄放入一個對象文件或可執行文件中。
常用的lib關鍵字,可以幫我們連入一個庫文件。
(8)#pragma pack()
我們知道在VC中,對於想結構體Struct這樣的類型,VC採用8字節對齊的方式,如果我們不想使用8字節對齊(在網絡變成中經常需要這樣),我們可以在結構體前面加上
#pragma pack(1)
struct
{
......
}
#pragma pack()
◇細說#pragma pack(n)
【參考】:http://blog.donews.com/kingle/archive/2005/07/02/451422.aspx
在C語言中,結構是一種複合數據類型,其構成元素既可以是基本數據類型(如int、long、float等)的變量,也可以是一些複合數據類型(如數組、結構、聯合等)的數據單元。在結構中,編譯器爲結構的每個成員按其自然對界(alignment)條件分配空間。各個成員按照它們被聲明的順序在內存中順序存儲,第一個成員的地址和整個結構的地址相同。
例如,下面的結構各成員空間分配情況:
struct test
{
char x1;
short x2;
float x3;
char x4;
};
結構的第一個成員x1,其偏移地址爲0,佔據了第1個字節。第二個成員x2爲short類型,佔據2個字節,則其起始地址必須按2字節對界,因此,編譯器在x2和x1之間填充了一個空字節。結構的第三個成員x3和第四個成員x4恰好落在其自然對界地址上,在它們的前面不需要額外的填充字節。因爲,在test結構中的成員x3要求4字節對界,是該結構所有成員中要求的最大對界單元,則test結構的自然對界條件爲4字節,編譯器在成員x4後面填充了3個空字節。整個結構所佔據空間爲12字節。
更改C編譯器的缺省字節對齊方式
在缺省情況下,C編譯器爲每一個變量或是數據單元按其自然對界條件分配空間。一般地,可以通過下面的方法來改變缺省的對界條件:
· 使用僞指令#pragma pack (n),C編譯器將按照n個字節對齊。
· 使用僞指令#pragma pack (),取消自定義字節對齊方式。
另外,還有如下的一種方式:
· __attribute((aligned (n))),讓所作用的結構成員對齊在n字節自然邊界上。如果結構中有成員的長度大於n,則按照最大成員的長度來對齊。
· __attribute__ ((packed)),取消結構在編譯過程中的優化對齊,按照實際佔用字節數進行對齊。
以上的n = 1, 2, 4, 8, 16... 第一種方式較爲常見。
( via http://blog.csdn.net/wenddy112/articles/300583.aspx )
下面有一道在 CSDN論壇 上討論火熱的題:
Intel和微軟和本公司同時出現的面試題
#pragma pack(8)
struct s1{
short a;
long b;
};
struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()
問
1.sizeof(s2) = ?
2.s2的c後面空了幾個字節接着是d?
解答結果如下:
sizeof(S2)結果爲24.
成員對齊有一個重要的條件,即每個成員分別對齊.即每個成員按自己的方式對齊.
也就是說上面雖然指定了按8字節對齊,但並不是所有的成員都是以8字節對齊。其對齊的規則是,每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這裏是8字節)中較小的一個對齊。並且結構的最終長度必須爲所有使用過的對齊參數中最大對齊參數的整數倍,不夠就補空字節。
S1中,成員a是2字節,默認按2字節對齊。指定對齊參數爲8,故這兩個值中取2,a按2字節對齊;成員b是4個字節,默認是按4字節對齊,這時就按4字節對齊,所以sizeof(S1)應該爲8;
S2中,c按1字節對齊,而d 是個結構,它是8個字節,它按什麼對齊呢?對於以結構作成員來說,它的默認對齊方式就是它的所有成員使用的對齊參數中最大的一個,S1的就是4.所以,成員d就是按4字節對齊.成員e是8個字節,它是默認按8字節對齊,和指定的一樣,所以它對到8字節的邊界上,這時,已經使用了12個字節了,所以又添加了4個字節的空,從第16個字節開始放置成員e.這時,長度爲24,已經可以被8(成員e按8字節對齊)整除.這樣,一共使用了24個字節.
a b
S1的內存佈局:11**,1111,
c S1.a S1.b d
S2的內存佈局:1***,11**,1111,****11111111
這裏有三點很重要:
1.每個成員分別按自己的方式對齊,並能最小化長度
2.複雜類型(如結構)的默認對齊方式是它最長的成員的對齊方式,這樣在成員是複雜類型時,可以最小化長度
3.對齊後的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時可以保證每一項都邊界對齊
補充一下,對於數組,比如:
char a[3];這種,它的對齊方式和分別寫3個char是一樣的.也就是說它還是按1個字節對齊.
如果寫: typedef char Array3[3];
Array3這種類型的對齊方式還是按1個字節對齊,而不是按它的長度.
不論類型是什麼,對齊的邊界一定是1,2,4,8,16,32,64....中的一個.
測試的編譯器:
GCC 2.95 3.1 3.3 3.4 4.0
MS C/C++ 7.0 7.1 8.0 beta
Borland C/C++ 5.6 6.0
Intel C/C++ 7.0 8.0 8.1
DigitalMars C/C++ 8.4
OpenWatcom 1.3
Codeplay C/C++ <?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />2.1.7
◇C++中的位域
C++也可以用位域(bit-field)作爲的類的數據成員,用來聲明存放特定數目的位。位域必須是有序數據類型。它可以有符號,也可以無符號。例如:
class File {
// ...
unsigned int modified : 1; // 位域 (bit-field)
};
位域標識符後面同樣跟有一個冒號,然後是一個常量表達式指定位數,例如,modified 是一個只有一位構成的位域。位域可以無位域名,這時只用來作填充或調整位置,而不能使用的
在類體中相鄰定義的位域,它們可能會被放在同一字節的連續位中,從對存儲空間壓縮。例如,在下列聲明中的5 個位域被存儲在單個unsigned int中(C++中必須存儲在同一個雙字空間中,而在C中一個位域必須存儲在同一個字節中),它首先與位域mode 相關聯。
typedef unsigned int Bit;
class File {
public:
Bit mode: 2;
Bit modified: 1;
Bit prot_owner: 3;
Bit prot_group: 3;
Bit prot_world: 3;
// ...
};
對於位域的訪問方式與其他類數據成員相同。例如,類的私有位域只能在類的成員函數和友元中被訪問。
下面的例子說明了怎樣使用大於1 位的位域:
enum { READ = 01, WRITE = 02 }; // 文件模式
int main() {
File myFile;
myFile.mode |= READ;
if ( myFile.mode & READ )
cout << "myFile.mode is set to READ/n";
}
通常情況下我們會定義一組inline 成員函數來測試每個位域成員的值,例如類File可以定義成員isRead()和isWrite():
inline int File::isRead() { return mode & READ; }
inline int File::isWrite() { return mode & WRITE; }
if ( myFile.isRead() ) /* ... */
有了這些成員函數,現在位域可以被聲明爲類File 的私有成員。
由於取地址操作符&不能被應用在位域上,所以也沒有能指向類的位域的指針,位域也不能是類的靜態成員。
C++標準庫提供了一個bitset 類模板,它可以輔助操縱位的集合,在可能的情況下應儘可能使用它來取代位域。