程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C/C++ 宏定義

C/C++ 宏定義

編輯:C++入門知識

1. 簡單宏定義     簡單的宏定義有如下格式:   [#define指令(簡單的宏)]  #define  標識符替換列表   替換列表是一系列的C語言記號,包括標識符、關鍵字、數、字符常量、字符串字面量、運算符和標點符號。當預處理器遇到一個宏定義時,會做一個 “標識符”代表“替換列表”的記錄。在文件後面的內容中,不管標識符在任何位置出現,預處理器都會用替換列表代替它。      不要在宏定義中放置任何額外的符號,否則它們會被作為替換列表的一部分。一種常見的錯誤是在宏定義中使用 = :     #define N = 100       /*** WRONG ***/   int a[N];            /* 會成為 int a[= 100]; */         在上面的例子中,我們(錯誤地)把N定義成一對記號(= 和100)。   在宏定義的末尾使用分號結尾是另一個常見錯誤:   #define N 100;       /*** WRONG ***/   int a[N];            /*    become int a[100;]; */     這裡N被定義為100和;兩個記號。   在一個宏定義中,編譯器可以檢測到絕大多數由多余符號所導致的錯誤。但不幸的是,編譯器會將每一處使用這個宏的地方標為錯誤,而不會直接找到錯誤的根源——宏定義本身,因為宏定義已經被預處理器刪除了。     簡單的宏主要用來定義那些被Kernighan和Ritchie稱為“明示常量”(manifest constant)的東西。使用宏,我們可以給數值、字符和字符串命名。   #define STE_LEN 80      #defineTRUE     1      #defineFALSE    0      #definePI        3.14159      #defineCR        '\r'      #defineEOS       '\0'     使用#define來為常量命名有許多顯著的優點:   1) 、 程序會更易讀。一個認真選擇的名字可以幫助讀者理解常量的意義。否則,程序將包含大量的“魔法數”,使讀者難以理解。   2) 、 程序會更易於修改。我們僅需要改變一個宏定義,就可以改變整個程序中出現的所有該常量的值。“硬編碼的”常量會更難於修改,特別是有時候當他們以稍微不同的形式出現時。(例如,如果一個程序包含一個長度為100的數組,它可能會包含一個從0到99的循環。如果我們只是試圖找到所有程序中出現的100,那麼就會漏掉99。)   3) 、可以幫助避免前後不一致或鍵盤輸入錯誤。假如數值常量3.14159在程序中大量出現,它可能會被意外地寫成3.1416或3.14195。   雖然簡單的宏常用於定義常量名,但是它們還有其他應用。   4) 、可以對C語法做小的修改。實際上,我們可以通過定義宏的方式給C語言符號添加別名,從而改變C語言的語法。例如,對於習慣使用Pascal的begin和end(而不是C語言的{和})的程序員,可以定義下面的宏:   #define BEGIN  {      #define END    }     我們甚至可以發明自己的語言。例如,我們可以創建一個LOOP“語句”,來實現一個無限循環:   #define LOOP   for (;;)   當然,改變C語言的語法通常不是個好主意,因為它會使程序很難被其他程序員所理解。   5) 、對類型重命名。在5.2節中,我們通過重命名int創建了一個Boolean類型:   #define BOOL int     雖然有些程序員會使用宏定義的方式來實現此目的,但類型定義(7.6節)仍然是定義新類型的最佳方法。   6) 、控制條件編譯。如將在14.4節中看到的那樣,宏在控制條件編譯中起重要的作用。例如,在程序中出現的宏定義可能表明需要將程序在“調試模式”下進行編譯,來使用額外的語句輸出調試信息:   #define DEBUG     這裡順便提一下,如上面的例子所示,宏定義中的替換列表為空是合法的。   當宏作為常量使用時,C程序員習慣在名字中只使用大寫字母。但是並沒有如何將用於其他目的的宏大寫的統一做法。由於宏(特別是帶參數的宏)可能是程序中錯誤的來源,所以一些程序員更喜歡使用大寫字母來引起注意。其他人則傾向於小寫,即按照Kernighan和Ritchie編寫的The C Programming Language一書中的樣式。         2. 帶參數的宏   帶參數的宏定義有如下格式:   [#define指令—帶參數的宏]  #define 標識符(x1, x2,…,xn)替換列表   其中x1, x2,…,xn是標識符(宏的參數)。這些參數可以在替換列表中根據需要出現任意次。      在宏的名字和左括號之間必須沒有空格。如果有空格,預處理器會認為是在定義一個簡單的宏,其中(x1,x2,…,xn)是替換列表的一部分。     當預處理器遇到一個帶參數的宏,會將定義存儲起來以便後面使用。在後面的程序中,如果任何地方出現了標識符(y1,y2,…,yn)格式的宏調用(其中y1,y2,…,yn是一系列標記),預處理器會使用替換列表替代,並使用y1替換x1,y2替換x2,依此類推。   例如,假定我們定義了如下的宏:   #define MAX(x,y)    ((x)>(y) ? (x) :(y))      #define IS_EVEN(n)   ((n)%2==0)     現在如果後面的程序中有如下語句:   i = MAX(j+k, m-n);      if (IS_EVEN(i)) i++;     預處理器會將這些行替換為   i = ((j+k)>(m-n)?(j+k):(m-n));   if (((i)%2==0)) i++;     如這個例子所顯示的,帶參數的宏經常用來作為一些簡單的函數使用。MAX類似一個從兩個值中選取較大的值的函數。IS_EVEN則類似於另一種函數,該函數當參數為偶數時返回1,否則返回0。   下面的例子是一個更復雜的宏:   #define TOUPPER(c)('a'<=(c)&&(c)<='z'?(c)-'a'+'A':(c))     這個宏檢測一個字符c是否在'a'與'z'之間。如果在的話,這個宏會用'c'減去'a'再加上'A',來計算出c所對應的大寫字母。如果c不在這個范圍,就保留原來的c。像這樣的字符處理的宏非常有用,所以C語言庫在<ctype.h>(23.4節)中提供了大量的類似的宏。其中之一就是toupper,與我們上面的TOUPPER例子作用一致(但會更高效,可移植性也更好)。   帶參數的宏可以包含空的參數列表,如下例所示:   #define getchar() getc(stdin)     空的參數列表不是一定確實需要,但可以使getchar更像一個函數。(沒錯,這就是<stdio.h>中的getchar,getchar的確就是個宏,不是函數——雖然它的功能像個函數。)             使用帶參數的宏替代實際的函數的優點:   1) 、  程序可能會稍微快些。一個函數調用在執行時通常會有些額外開銷——存儲上下文信息、復制參數的值等。而一個宏的調用則沒有這些運行開銷。   2) 、 宏會更“通用”。與函數的參數不同,宏的參數沒有類型。因此,只要預處理後的程序依然是合法的,宏可以接受任何類型的參數。例如,我們可以使用MAX宏從兩個數中選出較大的一個,數的類型可以是int,long int,float,double等等。           但是帶參數的宏也有一些缺點。   1) 、 編譯後的代碼通常會變大。每一處宏調用都會導致插入宏的替換列表,由此導致程序的源代碼增加(因此編譯後的代碼變大)。宏使用得越頻繁,這種效果就越明顯。當宏調用嵌套時,這個問題會相互疊加從而使程序更加復雜。思考一下,如果我們用MAX宏來找出3個數中最大的數會怎樣?   n = MAX(i, MAX(j,k));     下面是預處理後的這條語句:   n=((i)>(((j)>(k)?(j):(k)))?(i):(((j)>(k)?(j):(k))));     2) 、宏參數沒有類型檢查。當一個函數被調用時,編譯器會檢查每一個參數來確認它們是否是正確的類型。如果不是,或者將參數轉換成正確的類型,或者由編譯器產生一個出錯信息。預處理器不會檢查宏參數的類型,也不會進行類型轉換。   3) 、無法用一個指針來指向一個宏。如在17.7節中將看到的,C語言允許指針指向函數。這一概念在特定的編程條件下非常有用。宏會在預處理過程中被刪除,所以不存在類似的“指向宏的指針”。因此,宏不能用於處理這些情況。   4) 、宏可能會不止一次地計算它的參數。函數對它的參數只會計算一次,而宏可能會計算兩次甚至更多次。如果參數有副作用,多次計算參數的值可能會產生意外的結果。考慮下面的例子,其中MAX的一個參數有副作用:   n = MAX(i++, j);     下面是這條語句在預處理之後的結果:   n =((i++)>(j)?(i++):(j));     如果i大於j,那麼i可能會被(錯誤地)增加了兩次,同時n可能被賦予了錯誤的值。      由於多次計算宏的參數而導致的錯誤可能非常難於發現,因為宏調用和函數調用看起來是一樣的。更糟糕的是,這類宏可能在大多數情況下正常工作,僅在特定參數有副作用時失效。為了自保護,最好避免使用帶有副作用的參數。     帶參數的宏不僅適用於模擬函數調用。他們特別經常被作為模板,來處理我們經常要重復書寫的代碼段。如果我們已經寫煩了語句     printf("%d"\n, x);       因為每次要顯示一個整數x都要使用它。我們可以定義下面的宏,使顯示整數變得簡單些:     #define PRINT_INT(x)    printf("%d\n", x)       一旦定義了PRINT_INT,預處理器會將這行     PRINT_INT(i/j);   //轉換為   printf("%d\n", i/j);         3. #運算符          宏定義可以包含兩個運算符:#和##。編譯器不會識別這兩種運算符相反,它們會在預處理時被執行。   #運算符將一個宏的參數轉換為字符串字面量(字符串字面量(string literal)是指雙引號引住的一系列字符,雙引號中可以沒有字符,可以只有一個字符,也可以有很多個字符),, 簡單說就是在對它所引用的宏變量通過替換後在其左右各加上一個雙引號. 它僅允許出現在帶參數的宏的替換列表中。(一些C程序員將#操作理解為“stringization(字符串化)”;其他人則認為這實在是對英語的濫用。)用比較官方的話說就是將語言符號(Token)轉化為字符串。           #運算符有大量的用途,這裡只來討論其中的一種。假設我們決定在調試過程中使用PRINT_INT宏作為一個便捷的方法,來輸出一個整型變量或表達式的值。#運算符可以使PRINT_INT為每個輸出的值添加標簽。下面是改進後的PRINT_INT:     #define PRINT_INT(x) printf(#x " = %d\n", x)       x之前的#運算符通知預處理器根據PRINT_INT的參數創建一個字符串字面量。因此,調用     PRINT_INT(i/j);   //會變為   printf("i/j" " = %d\n", i/j);       在C語言中相鄰的字符串字面量會被合並,因此上邊的語句等價於:     printf("i/j = %d\n", i/j);       當程序執行時,printf函數會同時顯示表達式i/j和它的值。例如,如果i是11,j是2的話,輸出為   i/j = 5   TIPI例子:     #define STR(x) #x       int main(int argc char** argv)   {       printf("%s\n", STR(It's a long string)); // 輸出 It's a long str       return 0;   }         4. ##運算符              在C語言的宏中,"##"被稱為 連接符(concatenator),它是一種預處理運算符, 用來把兩個語言符號(Token)組合成單個語言符號。 這裡的語言符號不一定是宏的變量。並且雙井號不能作為第一個或最後一個元素存在.     ##運算符可以將兩個記號(例如標識符)“粘”在一起,成為一個記號。(無需驚訝,##運算符被稱為“記號粘合”。)如果其中一個操作數是宏參數,“粘合”會在當形式參數被相應的實際參數替換後發生。考慮下面的宏:   如下例子:當MK_ID被調用時(比如MK_ID(1)),預處理器首先使用自變量(這個例子中是1)替換參數n。接著,預處理器將i和1連接成為一個記號(i1)。下面的聲明使用MK_ID創建了3個標識符:     #define MK_ID(n) i##n   int MK_ID(1), MK_ID(2), MK_ID(3);   //預處理後聲明變為:   int i1, i2, i3;               ##運算符不屬於預處理器經常使用的特性。實際上,想找到一些使用它的情況是比較困難的。為了找到一個有實際意義的##的應用,我們來重新思考前面提到過的MAX宏。如我們所見,當MAX的參數有副作用時會無法正常工作。一種解決方法是用MAX宏來寫一個max函數。遺憾的是,往往一個max函數是不夠的。我們可能需要一個實際參數是int值的max函數,還需要參數為float值的max函數,等等。除了實際參數的類型和返回值的類型之外,這些函數都一樣。因此,這樣定義每一個函數似乎是個很蠢的做法。            解決的辦法是定義一個宏,並使它展開後成為max函數的定義。宏會有唯一的參數type,它表示形式參數和返回值的類型。這裡還有個問題,如果我們是用宏來創建多個max函數,程序將無法編譯。(C語言不允許在同一文件中出現兩個同名的函數。)為了解決這個問題,我們是用##運算符為每個版本的max函數構造不同的名字。下面的例子:請注意宏的定義中是如何將type和_max相連來形成新函數名的。假如我們需要一個針對float值的max函數。     #define GENERIC_MAX (type)           \   type type##_max(type x,  type y)    \   {                                      \     return x > y ? x :y;              \   }   GENERIC_MAX(float)       //預處理器會將這行展開為下面的代碼: float float_max(float x, float y) { return x > y ? x :y; }   再如:     #define PHP_FUNCTION            ZEND_FUNCTION   #define ZEND_FUNCTION(name)             ZEND_NAMED_FUNCTION(ZEND_FN(name))   #define ZEND_FN(name) zif_##name   #define ZEND_NAMED_FUNCTION(name)       void name(INTERNAL_FUNCTION_PARAMETERS)   #define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, \   zval *this_ptr, int return_value_used TSRMLS_DC       PHP_FUNCTION(count);     //  預處理器處理以後, PHP_FUCNTION(count);就展開為如下代碼 void zif_count(int ht, zval *return_value, zval **return_value_ptr,           zval *this_ptr, int return_value_used TSRMLS_DC)       宏ZEND_FN(name)中有一個"##",它的作用一如之前所說,是一個連接符,將zif和宏的變量name的值連接起來。 以這種連接的方式以基礎,多次使用這種宏形式,可以將它當作一個代碼生成器,這樣可以在一定程度上減少代碼密度, 我們也可以將它理解為一種代碼重用的手段,間接地減少不小心所造成的錯誤。         5. 宏的通用屬性     現在我們已經討論過簡單的宏和帶參數的宏了,我們來看一下它們都需要遵守的規則。 1) 、宏的替換列表可以包含對另一個宏的調用。例如,我們可以用宏PI來定義宏TWO_PI: #definePI      3.14159 #defineTWO_PI  (2*PI) 當預處理器在後面的程序中遇到TWO_PI時,會將它替換成(2*PI)。接著,預處理器會重新檢查替換列表,看它是否包含其他宏的調用(在這個例子中,調用了宏PI)。預處理器會不斷重新檢查替換列表,直到將所有的宏名字都替換掉為止。      2) 、預處理器只會替換完整的記號,而不會替換記號的片斷。因此,預處理器會忽略嵌在標識符名、字符常量、字符串字面量之中的宏名。例如,假設程序含有如下代碼行:     #define SIZE 256   int BUFFER_SIZE;   if (BUFFER_SIZE> SIZE)      puts("Error : SIZEexceeded");   //預處理後,這些代碼行會變為:   int BUFFER_SIZE;   if (BUFFER_SIZE> 256)     puts("Error :SIZEexceeded");   標識符BUFFER_ZISE和字符串"Error:SIZE exceeded"沒有被預處理影響,雖然它們都包含SIZE。    3) 、一個宏定義的作用范圍通常到出現這個宏的文件末尾。由於宏是由預處理器處理的,他們不遵從通常的范圍規則。一個定義在函數中的宏並不是僅在函數內起作用,而是作用到文件末尾。   4) 、宏不可以被定義兩遍,除非新的定義與舊的定義是一樣的。小的間隔上的差異是允許的,但是宏的替換列表(和參數,如果有的話)中的記號都必須一致。   5) 、宏可以使用#undef指令“取消定義”。#undef指令有如下形式: [#undef指令]  #undef  標識符  其中標識符是一個宏名。例如,指令 #undef N 會刪除宏N當前的定義。(如果N沒有被定義成一個宏,#undef指令沒有任何作用。)#undef指令的一個用途是取消一個宏的現有定義,以便於重新給出新的定義。         6. 宏定義中圓括號         在我們前面定義的宏的替換列表中有大量的圓括號。確實需要它們嗎?答案是絕對需要。如果我們少用幾個圓括號,宏可能有時會得到意料之外的——而且是不希望有的結果。對於在一個宏定義中哪裡要加圓括號有兩條規則要遵守:   首先,如果宏的替換列表中有運算符,那麼始終要將替換列表放在括號中:   #define TWO_PI (2*3.14159) 其次,如果宏有參數,每次參數在替換列表中出現時都要放在圓括號中: #define SCALE(x) ((x)*10)   沒有括號的話,我們將無法確保編譯器會將替換列表和參數作為完整的表達式。編譯器可能會不按我們期望的方式應用運算符的優先級和結合性規則。   為了展示為替換列表添加圓括號的重要性,考慮下面的宏定義,其中的替換列表沒有添加圓括號: #define TWO_PI 2*3.14159   /* 需要給替換列表加圓括號 */ 在預處理時,語句 conversion_factor = 360/TWO_PI; //變為 conversion_factor = 360/2*3.14159; 除法會在乘法之前執行,產生的結果並不是期望的結果。 當宏有參數時,僅給替換列表添加圓括號是不夠的。參數的每一次出現都要添加圓括號。例如,假設SCALE定義如下: #define SCALE(x) (x*10)   /* 需要給x添加括號 */ 在預處理過程中,語句 j = SCALE(i+1); 變為 j = (i+1*10); 由於乘法的優先級比加法高,這條語句等價於 j = i+10; 當然,我們希望的是 j = (i+1)*10;   在宏定義中缺少圓括號會導致C語言中最讓人討厭的錯誤。程序通常仍然可以編譯通過,而且宏似乎也可以工作,僅在少數情況下會出錯。       7. 創建較長的宏     1. 較長的宏中的逗號運算符       在創建較長的宏時,逗號運算符會十分有用。特別是可以使用逗號運算符來使替換列表包含一系列表達式。例如,下面的宏會讀入一個字符串,再把字符串顯示出來:   #define ECHO(s) (get(s), puts(s))         gets函數和puts函數的調用都是表達式,因此使用逗號運算符連接它們是合法的。我們甚至可以把ECHO宏當作一個函數來使用: ECHO(str);   /* 替換為 (gets(str), puts(str)); */ 除了使用逗號運算符,我們也許還可以將gets函數和puts函數的調用放在大括號中形成復合語句: #define ECHO(s)  { gets(s);  puts(s);  } 遺憾的是,這種方式並不奏效。假如我們將ECHO宏用於下面的if語句: if (echo_flag)     ECHO(str);   else     gets(str);   //將ECHO宏替換會得到下面的結果:   if (echo_flag)     { gets(str); puts(str);  };   else     gets(str);     編譯器會將頭兩行作為完整的if語句: if (echo_flag)     { gets(str);  puts(str);  }             編譯器會將跟在後面的分號作為空語句,並且對else子句產生出錯信息,因為它不屬於任何if語句。我們可以通過記住永遠不要在ECHO宏後面加分號來解決這個問題。但是這樣做會使程序看起來有些怪異。逗號運算符可以解決ECHO宏的問題,但並不能解決所有宏的問題。假如一個宏需要包含一系列的語句,而不僅僅是一系列的表達式,這時逗號運算符就起不到幫助的作用了。因為它只能連接表達式,不能連接語句。解決的方法是將語句放在do循環中,並將條件設置為假:   2. 宏定義中的do-while循環do  do循環必須始終隨跟著一個分號,因此我們不會遇到在if語句中使用宏那樣的問題了。為了看到這個技巧(嗯,應該說是技術)的實際作用,讓我們將它用於ECHO宏中: #define ECHO(s)       \         do{           \              gets (s) ;      \              puts (s) ;      \         } while  (0)   當使用ECHO宏時,一定要加分號: ECHO(str);   /* becomes do {  gets(str); puts(str); } while (0);  */ 為什麼在宏定義時需要使用do-while語句呢? 我們知道do-while循環語句是先執行循環體再判斷條件是否成立, 所以說至少會執行一次。當使用do{ }while(0)時由於條件肯定為false,代碼也肯定只   執行一次, 肯定只執行一次的代碼為什麼要放在do-while語句裡呢? 這種方式適用於宏定義中存在多語句的情況。 如下所示代碼: #define TEST(a, b)  a++;b++;       if (expr)       TEST(a, b);   else       do_else();   代碼進行預處理後,會變成:   if (expr)       a++;b++;   else       do_else();             這樣if-else的結構就被破壞了if後面有兩個語句,這樣是無法編譯通過的,那為什麼非要do-while而不是簡單的用{}括起來呢。 這樣也能保證if後面只有一個語句。例如上面的例子,在調用宏TEST的時候後面加了一個分號, 雖然這個分號可有可無, 但是出於習慣我們一般都會寫上。 那如果是把宏裡的代碼用{}括起來,加上最後的那個分號。 還是不能通過編譯。 所以一般的多表達式宏定義中都采用do-while(0)的方式。   3. "空操作"的定義       了解了do-while循環在宏中的作用,再來看"空操作"的定義。在PHP源碼中,由於PHP需要考慮到平台的移植性和不同的系統配置, 所以需要在某些時候把一些宏的操作定義為空操作。例如在sapi\thttpd\thttpd.c   文件中的VEC_FREE(): #ifdef SERIALIZE_HEADERS       # define VEC_FREE() smart_str_free(&vec_str)   #else       # define VEC_FREE() do {} while (0)   #endif     這裡涉及到條件編譯,在定義了SERIALIZE_HEADERS宏的時候將VEC_FREE()定義為如上的內容,而沒有定義時, 不需要做任何操作,所以後面的宏將VEC_FREE()定義為一個空操作,不做任何操作,通   常這樣來保證一致性, 或者充分利用系統提供的功能。 有時也會使用如下的方式來定義“空操作”,這裡的空操作和上面的還是不一樣,例如很常見的Debug日志打印宏: #ifdef DEBUG   #   define LOG_MSG printf   #else   #   define LOG_MSG(...)   #endif     在編譯時如果定義了DEBUG則將LOG_MSG當做printf使用,而不需要調試,正式發布時則將LOG_MSG()宏定義為空, 由於宏是在預編譯階段進行處理的,所以上面的宏相當於從代碼中刪除了。   上面提到了兩種將宏定義為空的定義方式,看上去一樣,實際上只要明白了宏都只是簡單的代碼替換就知道該如何選擇了。     8. 預定義宏   在C語言中預定義了一些有用的宏,見表預定義宏。這些宏主要是提供當前編譯的信息。宏__LINE__和__STDC__是整型常量,其他3個宏是字符串字面量。 表預定義宏: __LINE__      被編譯的文件的行數 __FILE__      被編譯的文件的名字 __DATE__    編譯的日期(格式"Mmm dd yyyy") __TIME__     編譯的時間(格式"hh:mm:ss") __STDC__   如果編譯器接受標准C,那麼值為1         1)、 __DATE__宏和__TIME__宏指明程序編譯的時間。例如,假設程序以下面的語句開始:   printf("Wacky Windows (c) 1996 Wacky Software, Inc.\n");   printf("Compiled on %s at %s\n", __DATE__,__TIME__);   每次程序開始執行,程序都會顯示下面兩行: Wacky Windows (c) 1996 Wacky Software, Inc. Compiled on Dec 23 1996 at 22:18:48 這樣的信息可以幫助區分同一個程序的不同版本。 2)、我們可以使用__LINE__宏和__FILE__宏來找到錯誤。考慮下面這個檢測被零除的除法的發生位置的問題。當一個C程序因為被零除而導致中止時,通常沒有信息指明哪條除法運算導致錯誤。下面的宏可以幫助我們查明錯誤的根源: #define CHECK_ZERO(divisor)  \   if (divisor == 0)  \     printf("*** Attempt to divide byzero on line %d  "  \             "of file %s  ***\n",__LINE__, __FILE__) CHECK_ZERO宏應該在除法運算前被調用: CHECK_ZERO(j); k = i / j; 如果j是0,會顯示出如下形式的信息: *** Attempt to divide by zero on line 9 of file FOO.c *** 類似這樣的錯誤檢測的宏非常有用。實際上,C語言庫提供了一個通用的、用於錯誤檢測的宏——assert宏  再如: #line 838 "Zend/zend_language_scanner.c"   #line預處理用於改變當前的行號(__LINE__)和文件名(__FILE__)。 如上所示代碼,將當前的行號改變為838,文件名Zend/zend_language_scanner.c 它的作用體現在編譯器的編寫中,我們知道   編譯器對C 源碼編譯過程中會產生一些中間文件,通過這條指令, 可以保證文件名是固定的,不會被這些中間文件代替,有利於進行調試分析。           9. C語言中常用的宏     01: 防止一個頭文件被重復包含   #ifndef COMDEF_H #define COMDEF_H //頭文件內容 #endif 02: 重新定義一些類型   防止由於各種平台和編譯器的不同,而產生的類型字節數差異,方便移植。   typedef  unsigned char      boolean;     /* Boolean value type. */ typedef  unsigned long int  uint32;      /* Unsigned 32 bit value */ typedef  unsigned short     uint16;      /* Unsigned 16 bit value */ typedef  unsigned char      uint8;       /* Unsigned 8  bit value */ typedef  signed long int    int32;       /* Signed 32 bit value */ typedef  signed short       int16;       /* Signed 16 bit value */ typedef  signed char        int8;        /* Signed 8  bit value */   //下面的不建議使用 typedef  unsigned char     byte;         /* Unsigned 8  bit value type. */ typedef  unsigned short    word;         /* Unsinged 16 bit value type. */ typedef  unsigned long     dword;        /* Unsigned 32 bit value type. */ typedef  unsigned char     uint1;        /* Unsigned 8  bit value type. */ typedef  unsigned short    uint2;        /* Unsigned 16 bit value type. */ typedef  unsigned long     uint4;        /* Unsigned 32 bit value type. */ typedef  signed char       int1;         /* Signed 8  bit value type. */ typedef  signed short      int2;         /* Signed 16 bit value type. */ typedef  long int          int4;         /* Signed 32 bit value type. */ typedef  signed long       sint31;       /* Signed 32 bit value */ typedef  signed short      sint15;       /* Signed 16 bit value */ typedef  signed char       sint7;        /* Signed 8  bit value */   03: 得到指定地址上的一個字節或字 #define  MEM_B(x) (*((byte *)(x))) #define  MEM_W(x) (*((word *)(x)))   04: 求最大值和最小值   #define  MAX(x,y) (((x)>(y)) ? (x) : (y)) #define  MIN(x,y) (((x) < (y)) ? (x) : (y))   05: 得到一個field在結構體(struct)中的偏移量   #define FPOS(type,field) ((dword)&((type *)0)->field)   06: 得到一個結構體中field所占用的字節數 #define FSIZ(type,field) sizeof(((type *)0)->field)   07: 按照LSB格式把兩個字節轉化為一個Word   #define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])   08: 按照LSB格式把一個Word轉化為兩個字節 #define FLOPW(ray,val) (ray)[0] = ((val)/256); (ray)[1] = ((val) & 0xFF)  09: 得到一個變量的地址(word寬度)   #define B_PTR(var)  ((byte *) (void *) &(var)) #define W_PTR(var)  ((word *) (void *) &(var))   10: 得到一個字的高位和低位字節 #define WORD_LO(xxx)  ((byte) ((word)(xxx) & 255)) #define WORD_HI(xxx)  ((byte) ((word)(xxx) >> 8))   11: 返回一個比X大的最接近的8的倍數 #define RND8(x) ((((x) + 7)/8) * 8   12: 將一個字母轉換為大寫   #define UPCASE(c) (((c)>='a' && (c) <= 'z') ? ((c) – 0×20) : (c))   13: 判斷字符是不是10進值的數字   #define  DECCHK(c) ((c)>='0' && (c)<='9')   14: 判斷字符是不是16進值的數字   #define HEXCHK(c) (((c) >= '0' && (c)<='9') ((c)>='A' && (c)<= 'F') \ ((c)>='a' && (c)<='f'))   15: 防止溢出的一個方法 #define INC_SAT(val) (val=((val)+1>(val)) ? (val)+1 : (val))   16: 返回數組元素的個數 #define ARR_SIZE(a)  (sizeof((a))/sizeof((a[0])))   17: 返回一個無符號數n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n) #define MOD_BY_POWER_OF_TWO( val, mod_by ) ((dword)(val) & (dword)((mod_by)-1))   18: 對於IO空間映射在存儲空間的結構,輸入輸出處理 #define inp(port) (*((volatile byte *)(port))) #define inpw(port) (*((volatile word *)(port))) #define inpdw(port) (*((volatile dword *)(port))) #define outp(port,val) (*((volatile byte *)(port))=((byte)(val))) #define outpw(port, val) (*((volatile word *)(port))=((word)(val))) #define outpdw(port, val) (*((volatile dword *)(port))=((dword)(val)))   19: 使用一些宏跟蹤調試 ANSI標准說明了五個預定義的宏名。它們是: __LINE__ __FILE__ __DATE__ __TIME__ __STDC__   C++中還定義了 __cplusplus 如果編譯器不是標准的,則可能僅支持以上宏名中的幾個,或根本不支持。記住編譯程序也許還提供其它預定義的宏名。 __LINE__ 及 __FILE__ 宏指示,#line指令可以改變它的值,簡單的講,編譯時,它們包含程序的當前行數和文件名。 __DATE__ 宏指令含有形式為月/日/年的串,表示源文件被翻譯到代碼時的日期。 __TIME__ 宏指令包含程序編譯的時間。時間用字符串表示,其形式為: 分:秒 __STDC__ 宏指令的意義是編譯時定義的。一般來講,如果__STDC__已經定義,編譯器將僅接受不包含任何非標准擴展的標准C/C++代碼。如果實現是標准的,則宏__STDC__含有十進制常量1。如果它含有任何其它數,則實現是非標准的。 __cplusplus 與標准c++一致的編譯器把它定義為一個包含至少6為的數值。與標准c++不一致的編譯器將使用具有5位或更少的數值。   可以定義宏,例如:當定義了_DEBUG,輸出數據信息和所在文件所在行 #ifdef _DEBUG #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_) #else #define DEBUGMSG(msg,date) #endif 20: 宏定義防止錯誤使用小括號包含。 例如: 有問題的定義:#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr); bufp += nr;} 應該使用的定義: #difne DO(a,b) do{a+b;a++;}while(0) 例如: if(addr)       DUMP_WRITE(addr,nr);   else       do_somethong_else();   //宏展開以後變成這樣:   if(addr)       {memcpy(bufp,addr,nr); bufp += nr;};   else       do_something_else();     gcc 在碰到else前面的“;”時就認為if語句已經結束,因而後面的else不在if語句中。而采用do{} while(0)的定義,在任何情況下都沒有問題。而改為 #difne DO(a,b) do{a+b;a++;}while(0) 的定義則在任何情況下都不會出錯

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved