C語言預處理預編譯命令及宏定義詳解
.c 源程序 —– 編譯 —– 鏈接 —- exe —-運行 ——–>
程序翻譯環境和執行環境
翻譯環境:源代碼被轉換為可執行機器指令(二進制代碼)。
執行環境:用於實際執行代碼。
翻譯環境:詳解編譯+鏈接
1.組成程序的每個源文件通過編譯過程分別轉換成目標代碼。
2.每個目標文件由鏈接器捆綁在一起,形成一個單一而完整的可執行程序。
3.鏈接器同時也會引入標準C函數庫中任何被該程序所用到的函數,而且他可以搜索程序員個人的程序庫,將其需要的函數也鏈接到程序。
extern
聲明外部文件中的函數
1. 編譯 — 預處理/預編譯 test.c —- test.i
文本操作
#include 頭文件的包含
註釋刪除:使用空格替換註釋
#define 替換,所以宏無法進行調試。
……
2. 編譯 — 編譯 test.i —- test.s
把c語言代碼翻譯成匯編代碼
語法分析
詞法分析
語義分析
符號匯總
3. 編譯 — 匯編 test.s —- test.obj
把匯編代碼轉換成二進制代碼(指令)。
形成符號表。(符號+地址)
4. 鏈接 test.obj —- test.exe
合並段表
符號表的合並和重定位
運行環境
1.程序必須載入內存中。在有操作系統的環境中:一般這個由操作系統完成。在獨立的環境中,程序的載入必須由手工安排,也可能是通過可執行代碼置入隻讀內存來完成。
2.程序的執行便開始。接著便調用main函數。
3.開始執行程序代碼。這個時候程序將使用一個運行時堆棧(stack),存儲函數的局部變量和返回地址。程序同時也可以使用靜態(static)內存,存儲於靜態內存中的變量在程序的整個執行過程一直保留他們的值。
4.終止程序。正常終止main函數;也有可能是意外終止
預處理/預編譯詳解
預定義符號
本來就有的符號
__FILE__ //進行編譯的源文件 __LINE__ //文件當前的行號 __DATE__ //文件被編譯的日期 __TIME__ //文件被編譯的時間 __STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
應用
printf("data: %s\n time: %s" ,__DATE__,__TIME__);
輸出
data: Jul 13 2021 time: 15:13:54
#define 定義標識符
宏
宏和define區別,宏是有參數的。
下面是宏的聲明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現在stuff中
參數列表的左括號必須與name緊鄰。如果兩者之間有任何空白存在,參數列表就會被解釋為stuf的一部分。
例如
#define SQUARE(X) (X)*(X) int main() { int ret = SQUARE(5); return 0; }
宏的參數是替換的,不是傳參的。
在定義宏的時候不要吝嗇括號。
#和##
#的作用
使用#
,把一個宏參數變成對應的字符串。
把參數插入到字符串中
#define PRINT(X) printf("the value of "#X" is %d\n", X) int main() { int a = 10; int b = 20; PRINT(a); PRINT(b); return 0; }
輸出
the value of a is 10
the value of b is 20
##的作用
##
可以把位於他兩邊的符號合成一個符號,允許宏定義從分離的文本片段創建創建標識符。
#define CAT(X,Y) X##Y int main() { int class84 = 2021; printf("%d\n", CAT(class, 84)); }
輸出
2021
帶副作用的宏參數
#define MAX(a, b) ( (a) > (b) ? (a) : (b) ) ... x = 5; y = 8; z = MAX(x++, y++); printf("x=%d y=%d z=%d\n", x, y, z); //輸出的結果是什麼?
這裡我們得知道預處理器處理之後的結果是什麼:
z = ( (x++) > (y++) ? (x++) : (y++));
輸出結果
x=6 y=10 z=9
宏和函數的對比
對於上述的宏,也可以用函數實現其功能。
使用宏的優點:
1.用於調用函數和從函數返回的代碼可能比實際執行這個小型計算工作需要的時間更多,所以宏比函數在程序的規模和速度方面更勝一籌。
2.函數的參數必須聲明為特定的類型,所以函數隻能在類型合適的表達式上使用。反之,這個宏可以用於整型、長整型、浮點數等等,宏是類型無關的。
使用宏的缺點:
1.每次調用宏,一份宏定義的代碼插入程序中,除非宏比較短,否則可能會大幅度增加代碼的長度。
2.宏無法調試。在預編譯(預處理)階段,已經把 # define 給替換瞭,已經不再是宏瞭。
3.宏由於類型無關,也就不夠嚴謹。
3.宏可能會帶來運算符優先級的問題,更容易導致程序出錯。
inline
內聯函數
命名約定
函數和宏語法相似,語言本身沒法幫我們區分二者。把宏名全部大寫,函數名不要全部大寫。
#undef 移除宏定義
這條指令用於移除宏定義。
如果現存的一個名字需要被重新定義,那麼他的舊名字首先要被移除。
#undef NAME
命令行定義
許多C的編譯器提供瞭一種能力,允許在命令行中定義符號,用於在啟動編譯過程。例如:當我們根據一個源文件要編譯出不同的一個程序的不同版本的時候,這個特性有點用處。假設某個程序中聲明瞭一個某個長度的數組,如果機器內存有限,我們需要一個很小的數組,但是另外一個機器內存大寫,我們需要一個數組能夠大寫。
條件編譯
#define DEBUG #ifdef DEBUG #endif
常見的條件編譯指令
#if 常量表達式 //... #endif
舉例子:為真參與編譯,為假 (0)不參與編譯。
#if 1 printf("balabala...."); #endif
二、多個分支的條件編譯
#if 常量表達式 //... #elif 常量表達式 //... #else //.... #endif
舉例子
#if 1==1 #elif 2==1 #else #endif
三、判斷是否被定義
#if defined(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol
四、嵌套指令
#if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #endif #ifdef OPTION2 unix_version_option2(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif
文件包含
我們已經知道,#include指令可以使另外一個文件被編譯。就像它實際出現於#include指令的地方一樣。這種替換的方式很簡單:預處理器先刪除這條指令,並用包含文件的內容替換。這樣一個源文件被包含10次,那就實際被編譯10次。
頭文件包含的方式:
1.本地文件包含:#include "Filename"
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找庫函數頭文件一樣在標準位置查找頭文件。如果找不到就提示編譯錯誤。
2.庫文件包含:#include <Filename.h>
查找策略:查找頭文件直接去標準路徑下去查找,如果找不到就返回錯誤信息。
這樣是不是可以說,對於庫文件也可以使用“”的形式包含?
答案是肯定的,可以。但是這樣做查找的效率就低些,當然這樣也不容易區分是庫文件還是本地文件瞭。
wwww想到自己經常重復包含,留下瞭悔恨的淚水~~
出現嵌套文件包含解決方法 :條件編譯
每個頭文件開頭這樣寫:
#ifndef __TEST__H__ #define __TEST__H__ //頭文件的內容 #endif //__TEST__H__
或者
#pragma once
就可以避免頭文件的重復引入。
總結一下:預處理階段的預處理指令:條件編譯指令 / #include / #define / #error /#pragma / ……
offsetof(宏類型,成員名字)偏移量模擬實現
#include <stdio.h> #include <stdlib.h> #include <stddef.h> struct S { char c1; int a; char c2; }; #define OFFSETOF(struct_name, member_name) (int)&(((struct_name*)0)->member_name) int main() { printf("%d\n", OFFSETOF(struct S, c1)); printf("%d\n", OFFSETOF(struct S, a)); printf("%d\n", OFFSETOF(struct S, c2)); return 0; }
以上就是C語言預處理預編譯命令及宏定義詳解的詳細內容,更多關於C語言預處理預編譯命令及宏的資料請關註WalkonNet其它相關文章!