C語言預(yù)處理預(yù)編譯命令及宏定義詳解
.c 源程序 ----- 編譯 ----- 鏈接 ---- exe ----運(yùn)行 -------->
程序翻譯環(huán)境和執(zhí)行環(huán)境
翻譯環(huán)境:源代碼被轉(zhuǎn)換為可執(zhí)行機(jī)器指令(二進(jìn)制代碼)。
執(zhí)行環(huán)境:用于實(shí)際執(zhí)行代碼。
翻譯環(huán)境:詳解編譯+鏈接
1.組成程序的每個源文件通過編譯過程分別轉(zhuǎn)換成目標(biāo)代碼。
2.每個目標(biāo)文件由鏈接器捆綁在一起,形成一個單一而完整的可執(zhí)行程序。
3.鏈接器同時也會引入標(biāo)準(zhǔn)C函數(shù)庫中任何被該程序所用到的函數(shù),而且他可以搜索程序員個人的程序庫,將其需要的函數(shù)也鏈接到程序。
extern
聲明外部文件中的函數(shù)
1. 編譯 — 預(yù)處理/預(yù)編譯 test.c ---- test.i
文本操作
#include 頭文件的包含
注釋刪除:使用空格替換注釋
#define 替換,所以宏無法進(jìn)行調(diào)試。
……
2. 編譯 — 編譯 test.i ---- test.s
把c語言代碼翻譯成匯編代碼
語法分析
詞法分析
語義分析
符號匯總
3. 編譯 — 匯編 test.s ---- test.obj
把匯編代碼轉(zhuǎn)換成二進(jìn)制代碼(指令)。
形成符號表。(符號+地址)
4. 鏈接 test.obj ---- test.exe
合并段表
符號表的合并和重定位
運(yùn)行環(huán)境
1.程序必須載入內(nèi)存中。在有操作系統(tǒng)的環(huán)境中:一般這個由操作系統(tǒng)完成。在獨(dú)立的環(huán)境中,程序的載入必須由手工安排,也可能是通過可執(zhí)行代碼置入只讀內(nèi)存來完成。
2.程序的執(zhí)行便開始。接著便調(diào)用main函數(shù)。
3.開始執(zhí)行程序代碼。這個時候程序?qū)⑹褂靡粋€運(yùn)行時堆棧(stack),存儲函數(shù)的局部變量和返回地址。程序同時也可以使用靜態(tài)(static)內(nèi)存,存儲于靜態(tài)內(nèi)存中的變量在程序的整個執(zhí)行過程一直保留他們的值。
4.終止程序。正常終止main函數(shù);也有可能是意外終止
預(yù)處理/預(yù)編譯詳解
預(yù)定義符號
本來就有的符號
__FILE__ //進(jìn)行編譯的源文件 __LINE__ //文件當(dāng)前的行號 __DATE__ //文件被編譯的日期 __TIME__ //文件被編譯的時間 __STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
應(yīng)用
printf("data: %s\n time: %s" ,__DATE__,__TIME__);
輸出
data: Jul 13 2021 time: 15:13:54
#define 定義標(biāo)識符
宏
宏和define區(qū)別,宏是有參數(shù)的。
下面是宏的聲明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一個由逗號隔開的符號表,它們可能出現(xiàn)在stuff中
參數(shù)列表的左括號必須與name緊鄰。如果兩者之間有任何空白存在,參數(shù)列表就會被解釋為stuf的一部分。
例如
#define SQUARE(X) (X)*(X) int main() { int ret = SQUARE(5); return 0; }
宏的參數(shù)是替換的,不是傳參的。
在定義宏的時候不要吝嗇括號。
#和##
#的作用
使用#
,把一個宏參數(shù)變成對應(yīng)的字符串。
把參數(shù)插入到字符串中
#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
##的作用
##
可以把位于他兩邊的符號合成一個符號,允許宏定義從分離的文本片段創(chuàng)建創(chuàng)建標(biāo)識符。
#define CAT(X,Y) X##Y int main() { int class84 = 2021; printf("%d\n", CAT(class, 84)); }
輸出
2021
帶副作用的宏參數(shù)
#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); //輸出的結(jié)果是什么?
這里我們得知道預(yù)處理器處理之后的結(jié)果是什么:
z = ( (x++) > (y++) ? (x++) : (y++));
輸出結(jié)果
x=6 y=10 z=9
宏和函數(shù)的對比
對于上述的宏,也可以用函數(shù)實(shí)現(xiàn)其功能。
使用宏的優(yōu)點(diǎn):
1.用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實(shí)際執(zhí)行這個小型計(jì)算工作需要的時間更多,所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌。
2.函數(shù)的參數(shù)必須聲明為特定的類型,所以函數(shù)只能在類型合適的表達(dá)式上使用。反之,這個宏可以用于整型、長整型、浮點(diǎn)數(shù)等等,宏是類型無關(guān)的。
使用宏的缺點(diǎn):
1.每次調(diào)用宏,一份宏定義的代碼插入程序中,除非宏比較短,否則可能會大幅度增加代碼的長度。
2.宏無法調(diào)試。在預(yù)編譯(預(yù)處理)階段,已經(jīng)把 # define 給替換了,已經(jīng)不再是宏了。
3.宏由于類型無關(guān),也就不夠嚴(yán)謹(jǐn)。
3.宏可能會帶來運(yùn)算符優(yōu)先級的問題,更容易導(dǎo)致程序出錯。
inline
內(nèi)聯(lián)函數(shù)
命名約定
函數(shù)和宏語法相似,語言本身沒法幫我們區(qū)分二者。把宏名全部大寫,函數(shù)名不要全部大寫。
#undef 移除宏定義
這條指令用于移除宏定義。
如果現(xiàn)存的一個名字需要被重新定義,那么他的舊名字首先要被移除。
#undef NAME
命令行定義
許多C的編譯器提供了一種能力,允許在命令行中定義符號,用于在啟動編譯過程。例如:當(dāng)我們根據(jù)一個源文件要編譯出不同的一個程序的不同版本的時候,這個特性有點(diǎn)用處。假設(shè)某個程序中聲明了一個某個長度的數(shù)組,如果機(jī)器內(nèi)存有限,我們需要一個很小的數(shù)組,但是另外一個機(jī)器內(nèi)存大寫,我們需要一個數(shù)組能夠大寫。
條件編譯
#define DEBUG #ifdef DEBUG #endif
常見的條件編譯指令
#if 常量表達(dá)式 //... #endif
舉例子:為真參與編譯,為假 (0)不參與編譯。
#if 1 printf("balabala...."); #endif
二、多個分支的條件編譯
#if 常量表達(dá)式 //... #elif 常量表達(dá)式 //... #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
文件包含
我們已經(jīng)知道,#include指令可以使另外一個文件被編譯。就像它實(shí)際出現(xiàn)于#include指令的地方一樣。這種替換的方式很簡單:預(yù)處理器先刪除這條指令,并用包含文件的內(nèi)容替換。這樣一個源文件被包含10次,那就實(shí)際被編譯10次。
頭文件包含的方式:
1.本地文件包含:#include "Filename"
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件。如果找不到就提示編譯錯誤。
2.庫文件包含:#include <Filename.h>
查找策略:查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就返回錯誤信息。
這樣是不是可以說,對于庫文件也可以使用“”的形式包含?
答案是肯定的,可以。但是這樣做查找的效率就低些,當(dāng)然這樣也不容易區(qū)分是庫文件還是本地文件了。
wwww想到自己經(jīng)常重復(fù)包含,留下了悔恨的淚水~~
出現(xiàn)嵌套文件包含解決方法 :條件編譯
每個頭文件開頭這樣寫:
#ifndef __TEST__H__ #define __TEST__H__ //頭文件的內(nèi)容 #endif //__TEST__H__
或者
#pragma once
就可以避免頭文件的重復(fù)引入。
總結(jié)一下:預(yù)處理階段的預(yù)處理指令:條件編譯指令 / #include / #define / #error /#pragma / ……
offsetof(宏類型,成員名字)偏移量模擬實(shí)現(xiàn)
#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語言預(yù)處理預(yù)編譯命令及宏定義詳解的詳細(xì)內(nèi)容,更多關(guān)于C語言預(yù)處理預(yù)編譯命令及宏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++初學(xué)者之根據(jù)輸入的任何一個正整數(shù),輸出可能被表示的連續(xù)正整數(shù)
這篇文章主要介紹了C++初學(xué)者之根據(jù)輸入的任何一個正整數(shù),輸出可能被表示的連續(xù)正整數(shù)的相關(guān)資料,需要的朋友可以參考下2016-03-03C++使用郵件槽實(shí)現(xiàn)ShellCode跨進(jìn)程傳輸
在計(jì)算機(jī)安全領(lǐng)域,進(jìn)程間通信(IPC)一直是一個備受關(guān)注的話題,在本文中,我們將探討如何使用Windows郵件槽(Mailslot)實(shí)現(xiàn)ShellCode的跨進(jìn)程傳輸,需要的可以參考下2023-12-12一起來學(xué)習(xí)C++的動態(tài)內(nèi)存管理
這篇文章主要為大家詳細(xì)介紹了C++的動態(tài)內(nèi)存管理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03詳解在C++中顯式默認(rèn)設(shè)置的函數(shù)和已刪除的函數(shù)的方法
這篇文章主要介紹了在C++中顯式默認(rèn)設(shè)置的函數(shù)和已刪除的函數(shù)的方法,文中講到了C++11標(biāo)準(zhǔn)中的新特性,需要的朋友可以參考下2016-01-01C++中typedef 及其與struct的結(jié)合使用
這篇文章主要介紹了C++中typedef 及其與struct的結(jié)合使用,需要的朋友可以參考下2014-02-02Qt數(shù)據(jù)庫應(yīng)用之實(shí)現(xiàn)圖片轉(zhuǎn)pdf
這篇文章主要為大家詳細(xì)介紹了如何利用Qt實(shí)現(xiàn)圖片轉(zhuǎn)pdf功能,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)或工作有一定參考價值,需要的可以了解一下2022-06-06