C語言##預算符
和#運算符一樣,##運算符可以用于宏函數的替換部分。這個運算符把兩個語言符號組合成單個語言符號。看例子:? ?#define XNAME(n) x ## n
如果這樣使用宏:
? ?XNAME(8)
則會被展開成這樣:
? ?x8
看明白了沒? ##就是個粘合劑,將前后兩部分粘合起來。
C語言#運算符
#也是預處理?是的,你可以這么認為。那怎么用它呢? 別急,先看下面例子:? ?#define SQR(x) printf("The square of x is %d.\n", ((x)*(x)));
如果這樣使用宏:
? ?SQR(8);
則輸出為:
? ?The square of x is 64.
注意到沒有,引號中的字符x 被當作普通文本來處理,而不是被當作一個可以被替換的語言符號。
假如你確實希望在字符串中包含宏參數,那我們就可以使用“#”,它可以把語言符號轉化為字符串。上面的例子改一改:
? ?#define SQR(x) printf("The square of "#x" is %d.\n", ((x)*(x)));
再使用:
? ?SQR(8);
則輸出的是:
? ?The square of 8 is 64.
很簡單吧?相信你現在已經明白#號的使用方法了。
C語言#pragma預處理
在所有的預處理指令中,#pragma 指令可能是最復雜的了,它的作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。#pragma 指令對每個編譯器給出了一個方法,在保持與C 和C ++語言完全兼容的情況下,給出主機或操作系統專有的特征。依據定義,編譯指示是機器或操作系統專有的,且對于每個編譯器都是不同的。其格式一般為:
? ?#pragma para
其中para 為參數,下面來看一些常用的參數。
一、#pragma message
message 參數:Message 參數是我最喜歡的一個參數,它能夠在編譯信息輸出窗口中輸出相應的信息,這對于源代碼信息的控制是非常重要的。其使用方法為:? ?#pragma message(“消息文本”)
當編譯器遇到這條指令時就在編譯輸出窗口中將消息文本打印出來。
當我們在程序中定義了許多宏來控制源代碼版本的時候,我們自己有可能都會忘記有沒有正確的設置這些宏,此時我們可以用這條指令在編譯的時候就進行檢查。假設我們希望判斷自己有沒有在源代碼的什么地方定義了_X86 這個宏可以用下面的方法
? ?#ifdef _X86
? ?#Pragma message(“_X86 macro activated!”)
? ?#endif
當我們定義了_X86 這個宏以后,應用程序在編譯時就會在編譯輸出窗口里顯示“_X86 macro activated!”。我們就不會因為不記得自己定義的一些特定的宏而抓耳撓腮了。
二、#pragma code_seg
另一個使用得比較多的pragma 參數是code_seg。格式如:? ?#pragma code_seg( ["section-name"[,"section-class"] ] )
它能夠設置程序中函數代碼存放的代碼段,當我們開發驅動程序的時候就會使用到它。
三、#pragma once
#pragma once (比較常用)只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次,這條指令實際上在Visual C++6.0 中就已經有了,但是考慮到兼容性并沒有太多的使用它。
四、#pragma hdrstop
#pragma hdrstop 表示預編譯頭文件到此為止,后面的頭文件不進行預編譯。BCB 可以預編譯頭文件以加快鏈接的速度,但如果所有頭文件都進行預編譯又可能占太多磁盤空間,所以使用這個選項排除一些頭文件。有時單元之間有依賴關系,比如單元A 依賴單元B,所以單元B 要先于單元A 編譯。
你可以用#pragma startup 指定編譯優先級,如果使用了#pragma package(smart_init) ,BCB就會根據優先級的大小先后編譯。
五、#pragma resource
#pragma resource "*.dfm"表示把*.dfm 文件中的資源加入工程。*.dfm 中包括窗體外觀的定義。六、#pragma warning
? ?#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)。
七、#pragma comment
#pragma comment(...)該指令將一個注釋記錄放入一個對象文件或可執行文件中。
常用的lib 關鍵字,可以幫我們連入一個庫文件。比如:
? ?#pragma comment(lib, "user32.lib")
該指令用來將user32.lib 庫文件加入到本工程中。
linker:將一個鏈接選項放入目標文件中,你可以使用這個指令來代替由命令行傳入的或者在開發環境中設置的鏈接選項,你可以指定/include 選項來強制包含某個對象,例如:
? ?#pragma comment(linker, "/include:__mySymbol")
八、#pragma pack
這里重點討論內存對齊的問題和#pragma pack()的使用方法。什么是內存對齊?先看下面的結構:
struct TestStruct1
{
? ?char c1;
? ?short s;
? ?char c2;
? ?int i;
};
假設這個結構的成員在內存中是緊湊排列的,假設c1 的地址是0,那么s 的地址就應該是1,c2 的地址就是3,i 的地址就是4。也就是c1 地址為00000000, s 地址為00000001, c2地址為00000003, i 地址為00000004。
可是,我們在Visual C++6.0 中寫一個簡單的程序:
struct TestStruct1 a;
printf("c1 %p, s %p, c2 %p, i %p\n",
(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.s - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,
(unsigned int)(void*)&a.i - (unsigned int)(void*)&a);
運行,輸出:
c1 00000000, s 00000002, c2 00000004, i 00000008。
為什么會這樣?這就是內存對齊而導致的問題。
1、為什么會有內存對齊?
字,雙字,和四字在自然邊界上不需要在內存中對齊。(對字,雙字,和四字來說,自然邊界分別是偶數地址,可以被4 整除的地址,和可以被8 整除的地址。)無論如何,為了提高程序的性能,數據結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了訪問未對齊的內存,處理器需要作兩次內存訪問;然而,對齊的內存訪問僅需要一次訪問。
一個字或雙字操作數跨越了4 字節邊界,或者一個四字操作數跨越了8 字節邊界,被認為是未對齊的,從而需要兩次總線周期來訪問內存。一個字起始地址是奇數但卻沒有跨越字邊界被認為是對齊的,能夠在一個總線周期中被訪問。某些操作雙四字的指令需要內存操作數在自然邊界上對齊。如果操作數沒有對齊,這些指令將會產生一個通用保護異常。
雙四字的自然邊界是能夠被16 整除的地址。其他的操作雙四字的指令允許未對齊的訪問(不會產生通用保護異常),然而,需要額外的內存總線周期來訪問內存中未對齊的數據。
缺省情況下,編譯器默認將結構、棧中的成員數據進行內存對齊。因此,上面的程序輸出就變成了:c1 00000000, s 00000002, c2 00000004, i 00000008。編譯器將未對齊的成員向后移,將每一個都成員對齊到自然邊界上,從而也導致了整個結構的尺寸變大。盡管會犧牲一點空間(成員之間有部分內存空閑),但提高了性能。也正是這個原因,我們不可以斷言sizeof(TestStruct1)的結果為8。在這個例子中,sizeof(TestStruct1)的結果為12。
2、如何避免內存對齊的影響?
那么,能不能既達到提高性能的目的,又能節約一點空間呢?有一點小技巧可以使用。比如我們可以將上面的結構改成:
struct TestStruct2
{
? ?char c1;
? ?char c2;
? ?short s;
? ?int i;
};
這樣一來,每個成員都對齊在其自然邊界上,從而避免了編譯器自動對齊。在這個例子中,sizeof(TestStruct2)的值為8。這個技巧有一個重要的作用,尤其是這個結構作為API的一部分提供給第三方開發使用的時候。第三方開發者可能將編譯器的默認對齊選項改變,從而造成這個結構在你的發行的DLL 中使用某種對齊方式,而在第三方開發者哪里卻使用另外一種對齊方式。這將會導致重大問題。
比如,TestStruct1 結構,我們的DLL 使用默認對齊選項,對齊為c1 00000000, s 00000002, c2 00000004, i 00000008,同時sizeof(TestStruct1)的值為12。
而第三方將對齊選項關閉,導致c1 00000000, s 00000001, c2 00000003, i 00000004,同時sizeof(TestStruct1)的值為8。
除此之外我們還可以利用#pragma pack()來改變編譯器的默認對齊方式(當然一般編譯器也提供了一些改變對齊方式的選項,這里不討論)。
使用指令#pragma pack (n),編譯器將按照n 個字節對齊。
使用指令#pragma pack (),編譯器將取消自定義字節對齊方式。
在#pragma pack (n)和#pragma pack ()之間的代碼按n 個字節對齊。但是,成員對齊有一個重要的條件,即每個成員按自己的方式對齊.也就是說雖然指定了按n 字節對齊,但并不是所有的成員都是以n 字節對齊。其對齊的規則是,每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這里是n 字節)中較小的一個對齊,即:min( n, sizeof( item )) 。并且結構的長度必須為所用過的所有對齊參數的整數倍,不夠就補空字節。看如下例子:
#pragma pack(8)
struct TestStruct4
{
? ?char a;
? ?long b;
};
struct TestStruct5
{
? ?char c;
? ?TestStruct4 d;
? ?long long e;
};
#pragma pack()
問題:
A)
sizeof(TestStruct5) = ?
B)
TestStruct5 的c 后面空了幾個字節接著是d?
TestStruct4 中,成員a 是1 字節默認按1 字節對齊,指定對齊參數為8,這兩個值中取1,a
按1 字節對齊;成員b 是4 個字節,默認是按4 字節對齊,這時就按4 字節對齊,所以sizeof(TestStruct4)應該為8;TestStruct5 中,c 和TestStruct4 中的a 一樣,按1 字節對齊,而d 是個結構,它是8 個字節,它
按什么對齊呢?對于結構來說,它的默認對齊方式就是它的所有成員使用的對齊參數中最大的一個, TestStruct4 的就是4.所以,成員d 就是按4 字節對齊.成員e 是8 個字節,它是默認按8字節對齊,和指定的一樣,所以它對到8 字節的邊界上,這時,已經使用了12 個字節了,所以又添加了4 個字節的空,從第16 個字節開始放置成員e.這時,長度為24,已經可以被8(成員e 按8字節對齊)整除.這樣,一共使用了24 個字節.內存布局如下(*表示空閑內存,1 表示使用內存。單位為1byete):
a b
TestStruct4 的內存布局:1***,1111,
c
TestStruct4.a TestStruct4.b d
TestStruct5 的內存布局: 1***, 1***, 1111, ****,11111111
這里有三點很重要:
首先,每個成員分別按自己的方式對齊,并能最小化長度。
其次,復雜類型(如結構)的默認對齊方式是它最長的成員的對齊方式,這樣在成員是復雜類型時,可以最小化長度。
然后,對齊后的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時可以保證每一項都邊界對齊。
補充一下,對于數組,比如:char a[3];它的對齊方式和分別寫3 個char 是一樣的.也就是說它還是按1 個字節對齊.如果寫: typedef char Array3[3];Array3 這種類型的對齊方式還是按1個字節對齊,而不是按它的長度。
但是不論類型是什么,對齊的邊界一定是1,2,4,8,16,32,64....中的一個。
另外,注意別的#pragma pack 的其他用法:
#pragma pack(push) //保存當前對其方式到packing stack
#pragma pack(push,n) 等效于
#pragma pack(push)
#pragma pack(n) //n=1,2,4,8,16 保存當前對齊方式,設置按n 字節對齊
#pragma pack(pop) //packing stack 出棧,并將對其方式設置為出棧的對齊方
C語言#line預處理
#line 的作用是改變當前行數和文件名稱,它們是在編譯程序中預先定義的標識符命令的基本形式如下:? ?#line number["filename"]
其中[]內的文件名可以省略。例如:
? ?#line 30 a.h
其中,文件名a.h 可以省略不寫。
這條指令可以改變當前的行號和文件名,例如上面的這條預處理指令就可以改變當前的行號為30,文件名是a.h。初看起來似乎沒有什么用,不過,他還是有點用的,那就是用在編譯器的編寫中,我們知道編譯器對C 源碼編譯過程中會產生一些中間文件,通過這條指令,可以保證文件名是固定的,不會被這些中間文件代替,有利于進行分析。
C語言#error預處理
#error 預處理指令的作用是,編譯程序時,只要遇到#error 就會生成一個編譯錯誤提示消息,并停止編譯。其語法格式為:? ?#error error-message
注意,宏串error-message 不用雙引號包圍。遇到#error 指令時,錯誤信息被顯示,可能同時還顯示編譯程序作者預先定義的其他內容。關于系統所支持的error-message 信息,請查找相關資料,這里不浪費篇幅來做討論。
C語言文件包含#include
文件包含是預處理的一個重要功能,它可用來把多個源文件連接成一個源文件進行編譯,結果將生成一個目標文件。C語言提供#include 命令來實現文件包含的操作,它實際是宏替換的延伸,有兩種格式:一、#include <filename>
其中,filename 為要包含的文件名稱,用尖括號括起來,也稱為頭文件,表示預處理到系統規定的路徑中去獲得這個文件(即C 編譯系統所提供的并存放在指定的子目錄下的頭文件)。找到文件后,用文件內容替換該語句。
2、#include “filename”
其中,filename 為要包含的文件名稱。雙引號表示預處理應在當前目錄中查找文件名為filename 的文件,若沒有找到,則按系統指定的路徑信息,搜索其他目錄。找到文件后,用文件內容替換該語句。
需要強調的一點是:#include 是將已存在文件的內容嵌入到當前文件中。
另外關于#include 的路徑也有點要說明:include 支持相對路徑,格式如trackant(蟻跡尋蹤)所寫:.代表當前目錄,..代表上層目錄。
C語言條件編譯#ifdef
條件編譯的功能使得我們可以按不同的條件去編譯不同的程序部分,因而產生不同的目標代碼文件。這對于程序的移植和調試是很有用的。條件編譯有三種形式,下面分別介紹:第一種形式:
#ifdef 標識符
? ?程序段1
#else
? ?程序段2
#endif
它的功能是,如果標識符已被#define 命令定義過則對程序段1 進行編譯;否則對程序段2進行編譯。如果沒有程序段2(它為空),本格式中的#else 可以沒有,即可以寫為:
#ifdef 標識符
? ?程序段
#endif
第二種形式:
#ifndef 標識符
? ?程序段1
#else
? ?程序段2
#endif
與第一種形式的區別是將“ifdef”改為“ifndef”。它的功能是,如果標識符未被#define 命令定義過則對程序段1 進行編譯,否則對程序段2 進行編譯。這與第一種形式的功能正相反。
第三種形式:
#if 常量表達式
? ?程序段1
#else
? ?程序段2
#endif
它的功能是,如常量表達式的值為真(非0),則對程序段1 進行編譯,否則對程序段2 進行編譯。因此可以使程序在不同條件下,完成不同的功能。
C語言宏定義#define
一、數值宏常量
#define 宏定義是個演技非常高超的替身演員,但也會經常耍大牌的,所以我們用它要慎之又慎。它可以出現在代碼的任何地方,從本行宏定義開始,以后的代碼就就都認識這個宏了;也可以把任何東西定義成宏。因為編譯器會在預編譯的時候用真身替換替身,而在我們的代碼里面卻又用常常用替身來幫忙。看例子:? ?#define PI 3.141592654
在此后的代碼中你盡可以使用PI 來代替3.141592654,而且你最好就這么做。不然的話,如果我要把PI 的精度再提高一些,你是否愿意一個一個的去修改這串數呢?你能保證不漏不出錯?而使用PI 的話,我們卻只需要修改一次。這種情況還不是最要命的,我們再看一個例子:
? ?#define ERROR_POWEROFF -1
如果你在代碼里不用ERROR_POWEROFF 這個宏而用-1,尤其在函數返回錯誤代碼的時候(往往一個開發一個系統需要定義很多錯誤代碼)。肯怕上帝都無法知道-1 表示的是什么意思吧。這個-1,我們一般稱為“魔鬼數”,上帝遇到它也會發狂的。所以,我奉勸你代碼里一定不要出現“魔鬼數”。
第一章我們詳細討論了const 這個關鍵字,我們知道const 修飾的數據是有類型的,而define 宏定義的數據沒有類型。為了安全,我建議你以后在定義一些宏常數的時候用const代替,編譯器會給const 修飾的只讀變量做類型校驗,減少錯誤的可能。但一定要注意const修飾的不是常量而是readonly 的變量,const 修飾的只讀變量不能用來作為定義數組的維數,也不能放在case 關鍵字后面。
二、字符串宏常量
除了定義宏常數之外,經常還用來定義字符串,尤其是路徑:A),#define ENG_PATH_1 E:\English\listen_to_this\listen_to_this_3
B),#define ENG_PATH_2 “E:\English\listen_to_this\listen_to_this_3”
噢,到底哪一個正確呢?如果路徑太長,一行寫下來比較別扭怎么辦?用反斜杠接續符啊:
C), #define ENG_PATH_3 E:\English\listen_to_this\listen\_to_this_3
還沒發現問題?這里用了4 個反斜杠,到底哪個是接續符?回去看看接續符反斜杠。
反斜杠作為接續符時,在本行其后面不能再有任何字符,空格都不行。所以,只有最后一個反斜杠才是接續符。至于A)和B),那要看你怎么用了,既然define 宏只是簡單的替換,那給ENG_PATH_1 加上雙引號不就成了:“ENG_PATH_1”。
但是請注意:有的系統里規定路徑的要用雙反斜杠“\\”,比如:
#define ENG_PATH_4 E:\\English\\listen_to_this\\listen_to_this_3
三、用define 宏定義注釋符號?
上面對define 的使用都很簡單,再看看下面的例子:#define BSC //
#define BMC /*
#define EMC */
D),BSC my single-line comment
E),BMC my multi-line comment EMC
D)和E)都錯誤,為什么呢?因為注釋先于預處理指令被處理,當這兩行被展開成//…或/*…*/時,注釋已處理完畢,此時再出現//…或/*…*/自然錯誤.因此,試圖用宏開始或結束一段注釋是不行的。
四、用define 宏定義表達式
這些都好理解,下面來點有“技術含量”的,定義一年有多少秒:? ?#define SEC_A_YEAR 60*60*24*365
這個定義沒錯吧?很遺憾,很有可能錯了,至少不可靠。你有沒有考慮在16 位系統下把這樣一個數賦給整型變量的時候可能會發生溢出?一年有多少秒也不可能是負數吧。修改一下:
? ?#define SEC_A_YEAR (60*60*24*365)UL
又出現一個問題,這里的括號到底需不需要呢?繼續看一個例子,定義一個宏函數,求x 的平方:
? ?#define SQR (x) x * x
對不對?試試:假設x 的值為10,SQR (x)被替換后變成10*10。沒有問題。
再試試:假設x 的值是個表達式10+1,SQR (x)被替換后變成10+1*10+1。問題來了,這并不是我想要得到的。怎么辦?括號括起來不就完了?
? ?#define SQR (x) ((x)*(x))
最外層的括號最好也別省了,看例子,求兩個數的和:
? ?#define SUM (x) (x)+(x)
如果x 的值是個表達式5*3,而代碼又寫成這樣:SUM (x)* SUM (x)。替換后變成:(5*3)+(5*3)*(5*3)+(5*3)。又錯了!所以最外層的括號最好也別省了。我說過define 是個演技高超的替身演員,但也經常耍大牌。要搞定它其實很簡單,別吝嗇括號就行了。
注意這一點:宏函數被調用時是以實參代換形參。而不是“值傳送”。
留四個問題:
A)
上述宏定義中“SUM”、“SQR”是宏嗎?
B)
#define EMPTY
這樣定義行嗎?
C)
打印上述宏定義的值:printf(“SUM (x)”);結果是什么?
D)
“#define M 100”是宏定義嗎?
五、宏定義中的空格
另外還有一個問題需要引起注意,看下面例子:? ?#define SUM (x) (x)+(x)
這還是定義的宏函數SUM(x)嗎?顯然不是。編譯器認為這是定義了一個宏:SUM,其代表的是(x) (x)+(x)。
為什么會這樣呢?其關鍵問題還是在于SUM 后面的這個空格。所以在定義宏的時候一定要注意什么時候該用空格,什么時候不該用空格。這個空格僅僅在定義的時候有效,在使用這個宏函數的時候,空格會被編譯器忽略掉。也就是說,上一節定義好的宏函數SUM(x)在使用的時候在SUM 和(x)之間留有空格是沒問題的。比如:SUM(3)和SUM (3)的意思是一樣的。
六、#undef
#undef 是用來撤銷宏定義的,用法如下:#define PI 3.141592654
…
// code
#undef PI
//下面的代碼就不能用PI 了,它已經被撤銷了宏定義。
也就是說宏的生命周期從#define 開始到#undef 結束。很簡單,但是請思考一下這個問題:
#define X 3
#define Y X*2
#undef X
#define X 2
int z=Y;
z 的值為多少?
C語言預處理命令有哪些
往往我說今天上課的內容是預處理時,便有學生質疑:預處理不就是include 和define么?這也用得著講啊?。是的,非常值得討論,即使是include 和define。但是預處理僅限于此嗎?遠遠不止。先看幾個個常識性問題:A)
預處理是C 語言的一部分嗎?
B)
包含“#”號的都是預處理嗎?
C)
預處理指令后面都不需要加“;”號嗎?
不要急著回答,先看看ANSI 標準定義的C 語言預處理指令:


- _LINE_ 表示正在編譯的文件的行號
- _FILE_ 表示正在編譯的文件的名字
- _DATE_ 表示編譯時刻的日期字符串,例如: "25 Dec 2007"
- _TIME_ 表示編譯時刻的時間字符串,例如: "12:30:55"
- _STDC_ 判斷該文件是不是定義成標準C 程序
如果編譯器不是標準的,則可能僅支持以上宏的一部分,或根本不支持。當然編譯器也有可能還提供其它預定義的宏名。注意:宏名的書寫由標識符與兩邊各二條下劃線構成。
相信很多初學者,甚至一些有經驗的程序員都沒有完全掌握這些內容,下面就一一詳細討論這些預處理指令。