溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C語言之預(yù)處理

發(fā)布時間:2020-10-01 04:18:33 來源:網(wǎng)絡(luò) 閱讀:281 作者:張濤澤 欄目:網(wǎng)絡(luò)安全
1 #define name value

  我再學(xué)習(xí)預(yù)處理直接的驅(qū)動力是看了php的源碼,開頭一大推的宏定義器,之前'掌握'的一點#define的用法太少了,根本看不懂源碼中宏的處理邏輯和運行的路徑。所以再學(xué)習(xí)預(yù)處理器很有必要,里面好多東西其實并不難,只是你沒有接觸到,等你學(xué)習(xí)了,就感覺容易了。

  一、宏定義和使用中的坑

  這小節(jié)采用先給代碼再說明的形式,這樣你可以看看每個代碼的運行結(jié)果是否和你預(yù)期的一致!

  宏是什么,宏就是#define機制把指定的參數(shù)替換的文本中,這樣的實現(xiàn)方式就是宏。使用宏定義可以抽出頻繁調(diào)用的函數(shù),加快執(zhí)行的速度。定義如下:#define name(參數(shù))  執(zhí)行體...  “參數(shù)”可以是使用逗號分隔的參數(shù)列表,這些參數(shù)可以被應(yīng)用到執(zhí)行體中,必須要注意的是“參數(shù)”的左括號必須和宏名字緊鄰,不然編輯器會報錯,或者被解釋成執(zhí)行體中的一部分。比如你寫了一個 TEST(a) a * a 調(diào)用執(zhí)行的時候?qū)懮?TEST(1) 實際執(zhí)行的是替換后的 1 * 1。

  凡事都有利弊,宏定義固然使用方便,并且有著函數(shù)不可比擬的執(zhí)行速度,但是宏定義中存在不少的坑,下面就說一說這個坑。看下面的代碼:

C語言之預(yù)處理

 1 #include <stdio.h> 2  3 #define TEST(a) a * a 4  5 int main() { 6     int b = TEST(2); 7     int c = TEST(1+2); 8     printf("b=%d, c=%d", b, c); 9     printf("\n\n");10 }

C語言之預(yù)處理

  沒有執(zhí)行的情況下,你感覺得到的結(jié)果是多少呢!好多人不加思索的說:b=4,c=9。如果真是這樣,就不存在坑了,實際打印出來是:b=4, c=5 ,為什么c的值和預(yù)想的會有偏差,其實你把執(zhí)行體中的值替換一下試試,就不難發(fā)現(xiàn)問題了,當(dāng)輸入1+2的時候,宏替換成了 1+2*1+2,當(dāng)然就是5了。好了明白了,那你學(xué)會了嗎?學(xué)會了再看一個:

C語言之預(yù)處理

 1 #include <stdio.h> 2  3 #define TEST(a,b) ((a) > (b) ? (a) : (b)) 4  5 int main() { 6     int zyf = 1; 7     int abc = 2; 8     int ret = TEST(zyf++, abc++); 9     printf("zyf=%d,abc=%d,ret=%d", zyf, abc, ret);10     printf("\n\n");11 }

C語言之預(yù)處理

  輸出多少呢,如果是 zyf=2,abc=3,ret=3 就錯了,實際結(jié)果是:zyf=2,abc=4,ret=3 。道理和前面的一樣,只看替換后的結(jié)果才能真正看到答案。

  這樣的問題防不勝防,怎樣才能解決呢,其實辦法很簡單,錯誤的原因是執(zhí)行的順序和我們預(yù)想的不一樣,那添加小括號應(yīng)該可以解決這種問題。 比如 (a) * (a)。這樣其實也不是最萬全的辦法,比如你看這個:ADD(a)  (a) + (a) ,如果這樣調(diào)用:ADD(2) * 5 ,這樣又不行了,被替換成了 (a) + (a) * 5 執(zhí)行順序和預(yù)想的還是不一樣,所以還要在最外層加上括號:((a) + (a)),這樣就解決了。

 

  、預(yù)定義符號

  C語言中有幾個預(yù)定義的符號,還是有必要和大家說上一說,先看一段代碼:

C語言之預(yù)處理

 1 #include <stdio.h> 2 #include <stdlib.h> 3 #define VAR_DUMP printf( \ 4     "[\n \tfile:%s\n" \ 5     "\tline:%d\n" \ 6     "\ttime:%s %s\n" \ 7     "\tvalue:%d\n]", \ 8     __FILE__, __LINE__, __DATE__, __TIME__, value \ 9 )10 int main() {11     int value = 1;12     VAR_DUMP;13     printf("\n\n");14 }

C語言之預(yù)處理

  是不是和你在大學(xué)學(xué)習(xí)的有點不一樣,最簡單的宏定義可以使用#define name value 的方式,當(dāng)然也可以把值寫成一個函數(shù),運行的時候直接替換函數(shù)。這個宏定義是封裝了調(diào)試方法,是打印變量內(nèi)容能像PHP中var_dump()或者print_r()函數(shù)一樣,打印出變量的內(nèi)容。

從這段代碼中能學(xué)習(xí)到幾點內(nèi)容:

1、使用#define可以使任何文本替換到程序中,在主程序中你可以隨意使用VAR_DUMP。

2、宏定義不以分號結(jié)束,如果非常長的宏定義,你可以在末尾加上反斜杠來分行,保持代碼易讀性。

3、你可以定義頻繁調(diào)用的函數(shù)為宏定義,這樣可以加快執(zhí)行的速速,具體原因后面會說到。

4、C語言有幾個預(yù)定的符號需要我們知道,很多時候特別有用:

  __FILE__ 預(yù)編譯的文件名

     __LINE__ 文件當(dāng)前行的行號(執(zhí)行到這一行)

  __DATE__ 文件編譯的日期

  __TIME__ 文件編譯的具體時間

     __STDC__ 是否遵循ANSI C (不常用)

最后附上運行結(jié)果,如圖:

C語言之預(yù)處理

  、宏替換的過程

  在程序的編譯階段,宏先被執(zhí)行替換,一般要涉及下面的步驟:

  1、調(diào)用宏的地方看是否 進(jìn)行了 #define定義,如果是就進(jìn)行替換。

  2、把替換的文本信息插入到替換的位置,其中參數(shù)被替換成了實際的值。

  3、#define可以包含其他定義的#define定義的東西,需要注意的是不能出現(xiàn)遞歸的情況。

  因為替換存在臨近字段自動結(jié)合,所以可以使用一些巧妙的方案:

C語言之預(yù)處理

 1 #include <stdio.h> 2 #include <stdlib.h> 3  4 #define VAR_DUMP(A,B)\ 5    printf("Value of " #B " is " A "\n", B) 6  7 int main(){ 8     int x = 1; 9     VAR_DUMP("%d", x+2);10 }

C語言之預(yù)處理

  

  、條件編譯和其他宏用法

  在大型的C程序中你能看到許多的條件編譯,比如可以根據(jù)當(dāng)前的環(huán)境加載不同的宏配置,或者在編譯的時候加上直極預(yù)設(shè)的編譯條件。這些東西的實現(xiàn)都離不開條件編譯。

  1、條件嵌套,#if   #endif 原型:

1 #if condition2     執(zhí)行體3 #endif

可以根據(jù)condition來確定執(zhí)行體要不要執(zhí)行,以此來控制在不同的環(huán)境下編譯成不同的系統(tǒng)。看下面的代碼,當(dāng)把DEBUG定義成非0值時,MAX宏定義是存在的,當(dāng)定義成0時,程序就會報錯。

C語言之預(yù)處理

 1 #include <stdio.h> 2  3 #define DEBUG 0 4 #if DEBUG 5     #define MAX(a) ((a) * (a)) 6 #endif 7  8 int main() { 9     int b = MAX(2);10     int c = MAX(1+2);11     printf("b=%d, c=%d", b, c);12     printf("\n\n");13 }

C語言之預(yù)處理

當(dāng)然#if 也可以與#elif嵌套使用,這樣就和我們在函數(shù)里使用if else一樣了,下面是一段php源碼中的一段話,你能看到編譯php指定不同的參數(shù),檢查不同的環(huán)境等等都可以通過預(yù)處理中的條件編譯開完成。

C語言之預(yù)處理

 1 #ifndef PHP_H 2     #define PHP_H 3  4     #ifdef HAVE_DMALLOC 5         #include <dmalloc.h> 6     #endif 7  8     #define PHP_API_VERSION 20100412 9     #define PHP_HAVE_STREAMS10     #define YYDEBUG 011 12     #include "php_version.h"13     #include "zend.h"14     #include "zend_qsort.h"15     #include "php_compat.h"16     #include "zend_API.h"17 18     #undef sprintf19     #define sprintf php_sprintf20 21     /* PHP's DEBUG value must match Zend's ZEND_DEBUG value */22     #undef PHP_DEBUG23     #define PHP_DEBUG ZEND_DEBUG24 25     #ifdef PHP_WIN3226     #    include "tsrm_win32.h"27     #    include "win95nt.h"28     #    ifdef PHP_EXPORTS29     #        define PHPAPI __declspec(dllexport)30     #    else31     #        define PHPAPI __declspec(dllimport)32     #    endif33     #    define PHP_DIR_SEPARATOR '\\'34     #    define PHP_EOL "\r\n"35     #else36     #    if defined(__GNUC__) && __GNUC__ >= 437     #        define PHPAPI __attribute__ ((visibility("default")))38     #    else39     #        define PHPAPI40     #    endif41 42     #    define THREAD_LS43     #    define PHP_DIR_SEPARATOR '/'44     #    define PHP_EOL "\n"45     #endif46 47     #ifdef NETWARE48         /* For php_get_uname() function */49         #define PHP_UNAME  "NetWare"50         #define PHP_OS      PHP_UNAME51     #endif52 53     #if HAVE_ASSERT_H54         #if PHP_DEBUG55             #undef NDEBUG56         #else57             #ifndef NDEBUG58                 #define NDEBUG59             #endif60         #endif61         #include <assert.h>62 63     #else /* HAVE_ASSERT_H */64         #define assert(expr) ((void) (0))65     #endif /* HAVE_ASSERT_H */66 67     #define APACHE 068     #if HAVE_UNIX_H69         #include <unix.h>70     #endif71 72     #if HAVE_ALLOCA_H73         #include <alloca.h>74     #endif75 76     #if HAVE_BUILD_DEFS_H77         #include <build-defs.h>78     #endif79     . . .

C語言之預(yù)處理

  2、是否已經(jīng)被定義

      被定義:#if   define() 或者是#ifdef 

  不被定義:#if !define() 或者是#ifndef

  前者的寫法雖然沒有后者精煉,但是前者有更多的使用場景,比如下面這種,可以進(jìn)行嵌套執(zhí)行。

1 #if defined(DEBUG)2      #ifdef DEBUGTWO3            #define TEST(a) a * a4      #endif5 #endif

  3、移除一個宏定義,當(dāng)不再使用一個宏定義后,可以使用undef來把不需要的宏移除,原型:

1 #undef name

 

  、宏命名規(guī)則和與函數(shù)區(qū)別

  從前面的使用中我們可以看到,宏的使用規(guī)則和函數(shù)真是一模一樣,但是本質(zhì)上還是有區(qū)別的,在使用中怎樣區(qū)別宏和函數(shù),涉及到代碼規(guī)范和代碼的可讀性問題。標(biāo)準(zhǔn)的宏使用應(yīng)該使用大寫字母,這樣在程序中任意地方使用宏都會知道這是一個宏定義。比如前面用到的 #define TEST(a) ((a) * (a))。

  宏與函數(shù)區(qū)別有以下幾點:

  1、執(zhí)行速度上,宏定義更快,函數(shù)因為需要調(diào)用棧,存在調(diào)用,返回,保存現(xiàn)場的系統(tǒng)開銷,所以比宏要慢。

  2、代碼長度上,宏在代碼長度上實際是增長的,每一處的使用宏都會把name替換成宏內(nèi)容如果大量使用,會是代碼顯著增長,新航道雅思培訓(xùn)函數(shù)代碼只有一份,比較節(jié)省代碼空間。

  3、參數(shù)類型上,宏沒有參數(shù)類型,只要可以 使用都行。函數(shù)不一樣,函數(shù)有參數(shù)類型確定性。正式因為這樣,有些宏能巧妙的利用這一點,完成函數(shù)不能完成的任務(wù),看下面代碼(書上看的),巧妙的利用傳遞類型無限制的特點自動開辟想要的各種類型空間:

C語言之預(yù)處理

1 #include <stdio.h>2 #include <stdlib.h>3 4 #define CREATE_P(nums, type) ((type *) malloc((nums) * sizeof(type)))5 6 int main(){7     int nums = 2;8     CREATE_P(nums, int);9 }

C語言之預(yù)處理

  4、宏定義和函數(shù)的使用場景,宏定義一般在程序的開頭,函數(shù)轉(zhuǎn)化成宏定義一定要考慮成本問題,短小精煉的函數(shù)轉(zhuǎn)化成宏使用時最好的,功能負(fù)責(zé)的函數(shù)轉(zhuǎn)化成宏就有點得不償失了。

 

  、文件包含

  1、本地文件包含和庫文件包含  

  文件包含在大型系統(tǒng)中必然會用到,大型系統(tǒng)宏定義巨多無比,不可能把所有的宏定義都復(fù)制到每個文件中,那么文件包含就能解決這種問題。

  實際上編輯器支持兩種文件包含,一種是我們經(jīng)常會用的庫文件的包含,比如上面我們看到的:#include <stdio.h>,還有一種是本地文件包含,說白了就是我們自己寫的文件,包含的原型如下:

1 #include <filename>2 #include "filename"

  這兩種方式都可以進(jìn)行文件的包含,不同的是第一種是庫文件的包含,標(biāo)準(zhǔn)的C庫函數(shù)都會以.h擴展名結(jié)尾,第二種是本地文件包含,當(dāng)編輯器看到第二種方式時,優(yōu)先查找本路徑下得本地庫文件,如果沒有找到就會像包含庫文件那樣在指定的路徑下去找,這時第二種和第一種就差不多了。第二種包含方式在編碼習(xí)慣上也是比較好的,別人看你的代碼很容易知道這個文件是庫函數(shù)還是你自己寫的。

  1、嵌套文件包含

  大型系統(tǒng)中不僅有大量的文件包含,還會有大量的嵌套文件包含,看下面的例子:

  a.h,b.h,c.h,define.c文件,其中a,b,c,define文件的內(nèi)容如下:

C語言之預(yù)處理

 1 a.h: 2 #include "c.h" 3 void var_dumpa(){ 4     test obja; 5     obja.a[1] = 2; 6     printf("obja.a[1]: %d\n", obja.a[1]); 7 } 8  9 b.h:10 #include "c.h"11 void var_dumpb(){12     test objb;13     objb.a[1] = 2;14     printf("objb.a[1]: %d\n", objb.a[1]);15 }16 17 c.h:18 #include <stdlib.h>19 #include <stdio.h>20 21 typedef struct test{22     int a[10];23 }test;24 25 define.c:26 #include <stdio.h>27 #include "a.h"28 #include "b.h"29 30 int main() {31     var_dumpa();32     var_dumpb();33     printf("\n\n");34 }

C語言之預(yù)處理

  ab文件包含c文件,define.c文件文件引用a,b文件后會引發(fā)一個錯誤:typedef struct test類型錯誤,因為c.h文件被包含了兩次,像這種情況在大型系統(tǒng)中會經(jīng)常遇到,或者說,你會發(fā)現(xiàn)重復(fù)引用庫文件也不會報錯,由此可見,庫文件一定是使用了解決辦法。其實解決這種錯誤的方案就是采用條件編譯,當(dāng)這個文件引入到另一個文件中后我們可以設(shè)置一個宏定義,比如:

C語言之預(yù)處理

1 #include <stdlib.h>2 #include <stdio.h>3 4 #ifndef PATH_C_H5     #define PATH_C_H 16     typedef struct test{7         int a[10];8     }test;9 #endif

C語言之預(yù)處理

因為每次編譯編譯器都會讀入整個頭文件,如果把所有的文件都加上這個條件編譯的話,那交叉引用文件產(chǎn)生的重復(fù)宏編譯問題就解決了,運行如下:

C語言之預(yù)處理

  好了,就寫這么多吧,重新梳理了對宏定義的認(rèn)識和基本的使用。時間倉促,出錯的地方請大嬸們一定指出,萬分感謝!


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI