?? 第5章 編譯預處理.txt
字號:
C語言編程常見問題解答
發表日期:2003年9月28日 已經有2865位讀者讀過此文
第5章 編譯預處理
本章集中討論與預處理程序有關的問題。在編譯程序對程序進行通常的編譯之前,要先運行預處理程序??赡苣阋郧皼]有見過這個程序,因為它通常在幕后運行,程序員是看不見它的,然而,這個程序非常有用。
預處理程序將根據源代碼中的預處理指令來修改你的程序。預處理指令(如#define)為預處理程序提供特定的指令,告訴它應該如何修改你的源代碼。預處理程序讀入所有包含的文件和待編譯的源代碼,經過處理生成源代碼的預處理版本。在該版本中,宏和常量標識符已用相應的代碼和值代替。如果源代碼中包含條件預處理指令(如#if),預處理程序將先判斷條件,然后相應地修改源代碼。
預處理程序有許多非常有用的功能,例如宏定義,條件編譯,在源代碼中插入預定義的環境變量,打開或關閉某個編譯選項,等等。對專業程序員來說,深入了解預處理程序的各種特征,是創建快速和高效的程序的關鍵之一。
在閱讀本章時,請記住本章采用的一些技術(以及所提到的一些常見陷阱),以便更好地利用預處理程序的各種功能。
5.1 什么是宏(macro)?怎樣使用宏?
宏是一種預處理指令,它提供了一種機制,可以用來替換源代碼中的字符串,宏是用“#define"語句定義的,下面是一個宏定義的例子:
#define VERSION—STAMP "1.02"
上例中所定義的這種形式的宏通常被稱為標識符。在上例中,標識符VERSION_STAMP即代表字符串"1.02"——在編譯預處理時,源代碼中的每個VERSION_STAMP標識符都將被字符串“1.02”替換掉。
以下是另一個宏定義的例子:
#define CUBE(x)((x),(x)*(x))
上例中定義了一個名為CUBE的宏,它有一個參數x。CUBE宏有自己的宏體,即((x)*(x)*(x))——在編譯預處理時,源代碼中的每個CUBE(x)宏都將被((x)*(x)*(x))替換掉。
使用宏有以下幾點好處;
(1)在輸入源代碼時,可省去許多鍵入操作。
(2)因為宏只需定義一次,但可以多次使用,所以使用宏能增強程序的易讀性和可靠性。
(3)使用宏不需要額外的開銷,因為宏所代表的代碼只在宏出現的地方展開,因此不會引起程序中的跳轉。
(4)宏的參數對類型不敏感,因此你不必考慮將何種數據類型傳遞給宏。
需要注意的是,在宏名和括起參數的括號之間絕對不能有空格。此外,為了避免在翻譯宏時產生歧義,宏體也應該用括號括起來。例如,象下例中這樣定義CUBE宏是不正確的:
denne CUBE(x) x * x * x
對傳遞給宏的參數也要小心,例如,一種常見的錯誤就是將自增變量傳遞給宏,請看下例:
#include <stdio. h>
#include CUBE(x) (x * x * x)
void main (void);
void main (void)
{
int x, y;
x = 5;
y = CUBE( + +x);
printfC'y is %d\n" . y);
}
在上例中,y究竟等于多少呢?實際上,y既不等于125(5的立方),也不等于336(6* 7*8),而是等于512。因為變量x被作為參數傳遞給宏時進行了自增運算,所以上例中的CUBE宏實際上是按以下形式展開的:
y = ((++x) * (++x) * (++x));
這樣,每次引用x時,x都要自增,所以你得到的結果與你預期的結果相差很遠,在上例中,由于x被引用了3次,而且又使用了自增運算符,因此,在展開宏的代碼時,x實際上為8,你將得到8的立方,而不5的立方。
上述錯誤是比較常見的,作者曾親眼見過有多年C語言編程經驗的人犯這種錯誤。因為在程序中檢查這種錯誤是非常費勁的,所以你要給予充分的注意。你最好試一下上面的例子,親眼看一下那個令人驚訝的結果值(512)。
宏也可使用一些特殊的運算符,例如字符串化運算符“#”和。連接運算符“##”?!?”運算符能將宏的參數轉換為帶雙引號的字符串,請看下例:
define DEBUG_VALUE(v) printf(#v"is equal to %d.\n",v)
你可以在程序中用DEBUG_VALUE宏檢查變量的值,請看下例:
int x=20;
DEBUG_VALUE(x);
上述語句將在屏幕上打印"x is equal to 20"。這個例子說明,宏所使用的“#”運算符是一種非常方便的調試工具。
“##”運算符的作用是將兩個獨立的字符串連接成一個字符串,詳見5.16。
請參見:
5.10 使用宏更好,還是使用函數更好?
5.16 連接運算符“##”有什么作用?
5.17 怎樣建立對類型不敏感的宏?
5.18 什么是標準預定義宏?
5.31 怎樣取消一個已定義的宏?
5.2 預處理程序(preprocessor)有什么作用?
C語言預處理程序的作用是根據源代碼中的預處理指令修改你的源代碼。預處理指令是一種命令語句(如#define),它指示預處理程序如何修改源代碼。在對程序進行通常的編譯處理之前,編譯程序會自動運行預處理程序,對程序進行編譯預處理,這部分工作對程序員來說是不可見的。
預處理程序讀入所有包含的文件以及待編譯的源代碼,然后生成源代碼的預處理版本。在預處理版本中,宏和常量標識符已全部被相應的代碼和值替換掉了。如果源代碼中包含條件預處理指令(如#if),那么預處理程序將先判斷條件,再相應地修改源代碼。
下面的例子中使用了多種預處理指令:
# include <stdio. h>
# define TRUE 1
# define FALSE (!TRUE)
# define GREATER (a, b) ((a) > (b) ? (TRUE) : (FALSE))
# define PIG-LATIN FALSE
void main (void);
void main (void)
{
int x, y;
# if PIG_LATIN
printf("Easeplay enternay ethay aluevay orfay xnay:") ;
scanf("%d", &x) ;
printf("Easeplay enternay ethay aluevay orfay ynay:");
scanf("%d", &y);
#else
printf(" Please enter the value for x: ");
scanf("%d", &x);
printf("Please enter the value for y: ");
scanf("%d", &y);
# endif
if (GREATER(x, y) = = TRUE)
{
# if PIG- LATIN
printf("xnay islay eatergray anthay ynay!\n");
#else
printf {" x is greater than y! \n" ) ;
# endif
}
else
{
# if PIG_LATIN
printf ("xnay islay otnay eatergray anthay ynay!\n");
#else
printf ("x is not greater than y!\n");
# endif
}
}
上例通過預處理指令定義了3個標識符常量(即TRUE,FALSE和PIG_LATIN)和一個宏(即GREATER(a,b)),并使用了一組條件編譯指令。當預處理程序處理上例中的源代碼時,它首先讀入stdio.h頭文件,并解釋其中的預處理指令,然后把所有標識符常量和宏用相應的值和代碼替換掉,最后判斷PIG_LATIN是否為TRUE,并由此決定是使用拉丁文還是
使用英文。
如果PIG_LATIN為FALSE,則上例的預處理版本將如下所示:
/ * Here is where all the include files
would be expanded. * /
void main (void)
{
int x, y;
printf("Please enter the value for X: ");
scanf("%d", &x);
printf("Please enter the value for y: ");
scanf("%d", &y),
if (((x) > (y) ? (1) : (!1)) == 1)
{
printf("x is greater than y!\n");
}
else
{
printf{"x is not greater than y!\n");
}
}
多數編譯程序都提供了一個命令行選項,或者有一個獨立的預處理程序,可以讓你只啟動預處理程序并將源代碼的預處理版本保存到一個文件中。你可以用這種方法查看源代碼的預處理版本,這對調試與宏或其它預處理指令有關的錯誤是比較有用的。
請參見:
5.3 怎樣避免多次包含同一個頭文件?
5.4 可以用#include指令包含類型名不是".h"的文件嗎?
5.12 #include <file>和#include"file"有什么不同?
5.22 預處理指令#pragma有什么作用?
5.23 #line有什么作用?
5.3 怎樣避免多次包含同一個頭文件?
通過#ifndef和#define指令,你可以避免多次包含同一個頭文件。在創建一個頭文件時,你可以用#define指令為它定義一個唯一的標識符名稱。你可以通過#ifndef指令檢查這個標識符名稱是否已被定義,如果已被定義,則說明該頭文件已經被包含了,就不要再次包含該頭文件;反之,則定義這個標識符名稱,以避免以后再次包含該頭文件。下述頭文件就使用了這種技術:
# ifndef _FILENAME_H
#define _FILENAME_H
#define VER_NUM " 1. 00. 00"
#define REL_DATE "08/01/94"
#if _WINDOWS_
# define OS_VER "WINDOWS"
#else
#define OS_VER "DOS"
# endif
# endif
當預處理程序處理上述頭文件時,它首先檢查標識符名稱_FILENAME_H是否已被定義——如果沒有被定義,預處理程序就對此后的語句進行預處理,直到最后一個#endif語句;反之,預處理程序就不再對此后的語句進行預處理。
請參見:
5.4 可以用#include指令包含類型名不是".h"的文件嗎?
5,12 #include <file>和#include“file”有什么不同?
5.14 包含文件可以嵌套嗎?
5.15 包含文件最多可以嵌套幾層?
5. 4 可以用#include指令包含類型名不是".h"的文件嗎?
預處理程序將包含用#include指令指定的任意一個文件。例如,如果程序中有下面這樣一條語句,那么預處理程序就會包含macros.inc文件。
#include <macros.inc>
不過,最好不要用#include指令包含類型名不是".h"的文件,因為這樣不容易區分哪些文件是用于編譯預處理的。例如,修改或調試你的程序的人可能不知道查看macros.inc文件中的宏定義,而在類型名為".h"的文件中,他卻找不到在macros.inc文件中定義的宏。如果將macros.inc文件改名為macros.h,就可以避免發生這種問題。
請參見:
5.3怎樣避免多次包含同一個頭文件?
5.12#include<file>和#include“file”有什么不同?
5,14包含文件可以嵌套嗎?
5.15包含文件最多可以嵌套幾層?
5.5 用#define指令說明常量有什么好處?
如果用#define指令說明常量,常量只需說明一次,就可多次在程序中使用,而且維護程序時只需修改#define語句,不必一一修改常量的所有實例。例如,如果在程序中要多次使用PI(約3.14159),就可以象下面這樣說明一個常量:
#define PI 3.14159
如果想提高PI的精度,只需修改在#define語句中定義的PI值,就不必在程序中到處修改了。通常,最好將#define語句放在一個頭文件中,這樣多個模塊就可以使用同一個常量了。
用#define指令說明常量的另一個好處是占用的內存最少,因為以這種方式定義的常量將直接進入源代碼,不需要再在內存中分配變量空間。
但是,這種方法也有缺點,即大多數調試程序無法檢查用#define說明的常量。
用#define指令說明的常量可以用#under指令取消。這意味著,如果原來定義的標識符(如NULL)不符合你的要求,你可以先取消原來的定義,然后重新按自己的要求定義一個標識符,詳見5.31。
請參見:
5.6用enum關鍵字說明常量有什么好處?
5.7與用#define指令說明常量相比,用enum關鍵字說明常量有什么好處?
5.31怎樣取消一個已定義的宏?
5.6 用enum關鍵字說明常量有什么好處?
用enum關鍵字說明常量(即說明枚舉常量)有三點好處:
(1)用enum關鍵字說明的常量由編譯程序自動生成,程序員不需要用手工對常量一一賦值。
(2)用enum關鍵字說明常量使程序更清晰易讀,因為在定義enum常量的同時也定義了一個枚舉類型標識符。
(3)在調試程序時通常可以檢查枚舉常量,這一點是非常有用的,尤其在不得不手工檢查頭文件中的常量值時。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -