宏
? 基本概念
C語言中可以利用宏定義實現文本的快速替換,注意:宏定義是單純的文本替換,不檢查語法是否合法。
C語言標準中提供了很多的預處理指令,比如#include、#pragma…以#開頭的都屬于預處理指令。
預處理指令指的是在gcc編譯套件中的cpp預處理器對程序進行編譯之前所做的一些動作,比如#include預處理指令就是在程序編譯之前由預處理器把包含的頭文件中的代碼拷貝一份到源文件對應的位置,如果包含的文件中還有其他的預處理指令,會遞歸執行!
C語言標準中還提供了#define預處理指令,define是C語言關鍵字之一,中文具有定義的含義,所以利用#define預處理指令可以對某些表達式、某些常量、某些函數進行定義,其實就是給這些內容起一個可讀性較高的名稱。
定義格式
宏替換其實就是簡單的文本替換,宏名稱就是一個用戶命名的特定的標識符,一般實際開發中宏名稱都采用大寫(潛規則)。 macro 宏
宏名稱后面就是用來替換宏名稱的替換列表,這個替換列表可以是常量、表達式、if語句以及函數等。
定義格式: #define 宏名稱(大寫) 替換列表 換行(一般就是用戶按下回車)
用戶在源文件中某個位置使用了宏,不管使用了多少次,在程序編譯之前,預處理器都會把宏用替換列表進行替換,當然要注意,宏替換就是單純的文本替換,預處理器并不會做任何檢查,比如替換之后是否符合語法,語法的檢查是由編譯器在編譯階段進行的。
使用規則
注意:宏定義的作用域是針對整個文件有效,所以應該定義在源文件的開頭部分,這樣才可以在其他的函數中使用宏定義,另外,宏不是語句,所以不需要在末尾添加分號,如果添加分號,則分號也會被一起替換。
具體分類
C語言中宏定義的方案有三種,分別是無參數宏、帶參數宏、無替換列表宏,具體如下所示:
(1) 無參數的宏定義
注意:除了用戶自定義的宏之外,系統中也存在一些已經定義好的宏,比如常用的NULL就是一個宏,當然,C99標準中還有一些常用的系統預定義的宏:
(2) 帶參數的宏定義
C語言標準中支持定義帶參數的宏,帶參數的宏的使用在語法上類似于函數調用,宏的參數由括號()進行包含,括號中如果有多個參數則需要通過逗號來分隔,另外,帶參數的宏在定義的時候宏名稱和參數列表之間不能空格,如下所示:
可以發現,帶參數的宏和函數的形式很像,但是卻完全不同,帶參數的宏會在程序所有出現的位置進行展開,缺點是浪費了內存空間,但是節約了函數切換的時間。
(3) 無替換列表的宏
C語言中也允許只定義一個宏,這個宏可以沒有替換列表,一般實際開發中都是對程序進行條件編譯的情況下來使用。
條件編譯指的是可以選擇性的編譯程序中的某段代碼,也就是預處理器可以根據具體的條件來保留或者刪除某段源程序。
可以理解為是類似于C語言的判斷語句,只不過是使用C語言中的預處理指令來判斷宏的有效性,有效性指的是宏是否為真以及宏是否存在,C語言中提供了多種預處理指令來實現條件編譯。
A. #if 用于判斷常量表達式是否成立,遵循“非0即真”原則,#if預處理指令作為條件編譯
一般#if和#endif是結合一起使用的,經常用于程序中的調試,可以選擇保留或注釋代碼塊!
B. #ifdef 用于判斷宏是否被定義,如果宏是提前定義好的,則該預處理指令是有效的,也需要和#endif一起使用
C.#if和#elif和#else和#endif 用于條件編譯,可以通過常量表達式的多種狀態來選擇保留或者刪除某些代碼塊
D. #ifndef和#endif 用于判斷宏是否未定義,如果宏定義,則該代碼塊會被刪除,如果宏未被定義,則該代碼塊可以保留
作用范圍
思考:宏定義一般定義在源文件的開頭,所以作用域是針對整個文件,但是有的時候如果只打算讓某個宏只對某個函數有效,請問應該如何實現?
回答:可以實現,可以利用C語言標準中提供的預處理指令#undef,可以提前終止某個宏的作用域。
程序的編譯過程
思考:什么叫做預處理階段?預處理階段和編譯階段有什么不同?源文件轉換為可執行文件一共需要經歷幾個階段?
預處理:
對源碼進行簡單的加工,GCC編譯器會調用預處理器cpp對程序進行預處理,其實就是解釋源程序中所有的預處理指令,如#include(文件包含)、#define(宏定義)、#if(條件編譯)等以#號開頭的預處理語句。
這些預處理指令將會在預處理階段被解釋掉,如會把被包含的文件拷貝進來,覆蓋掉原來的#include語句,把所有的宏定義展開,所有的條件編譯語句被執行,GCC還會把所有的注釋刪掉,添加必要的調試信息。
預處理指令: gcc -E xxx.c -o xxx.i 會生成預處理文件 xxx.i
編譯:
就是對經過預處理之后的.i文件進行進一步翻譯,也就是對語法、詞法的分析,最終生成對應硬件平臺的匯編文件,具體生成什么平臺的匯編文件取決于編譯器,比如X86平臺使用gcc編譯器,而ARM平臺使用交叉編譯工具arm-linux-gcc。
編譯指令 : gcc -S xxx.i -o xxx.s 會生成匯編文件 xxx.s
匯編:
GCC編譯器會調用匯編器as將匯編文件翻譯成可重定位文件,其實就是把.s文件的匯編代碼翻譯為相應的指令。
編譯指令 : gcc -c xxx.s -o xxx.o 會生成目標文件 xxx.o
鏈接:
經過匯編步驟后生成的.o文件其實是ELF格式的可重定位文件,雖然已經生成了指令流,但是需要重定位函數地址等,所以需要鏈接系統提供的標準C庫和其他的gcc基本庫文件等,并且還要把其他的.o文件一起進行鏈接。-lc -lgcc 是默認的,可以省略
編譯指令:gcc hello.o -o hello -lc -lgcc 會生成可執行文件 xxx // l是lib的縮寫