Gcc的編譯流程分為了四個(gè)步驟:
預(yù)處理,生成預(yù)編譯文件(.文件):gcc –E hello.c –o hello.i
編譯,生成匯編代碼(.s文件):gcc –S hello.i –o hello.s
匯編,生成目標(biāo)文件(.o文件):gcc –c hello.s –o hello.o
鏈接,生成可執(zhí)行文件:gcc hello.o –o hello
一、預(yù)處理
預(yù)編譯程序讀出源代碼,對(duì)其中內(nèi)嵌的指示字進(jìn)行響應(yīng),產(chǎn)生源代碼的修改版本,修改后的版本會(huì)被編譯程序讀入。
在 GNU 術(shù)語(yǔ)中,預(yù)處理程序叫做 CPP。而 GNU 的可執(zhí)行程序叫做 cpp。
簡(jiǎn)單來(lái)說(shuō),預(yù)處理就是將要包含(include)的文件插入原文件中、將宏定義展開(kāi)、根據(jù)條件編譯命令選擇要使用的代碼,最后將這些代碼輸出到一個(gè) '.i' 文件中等待進(jìn)一步處理。
預(yù)編譯過(guò)程主要處理那些源代碼文件中以 '#'開(kāi)始的預(yù)編譯指令。比如'#include'、'#define'等,主要處理規(guī)則如下:
將所有的 '#define' 刪除,并且展開(kāi)所有的宏定義
處理所有條件預(yù)編譯指令,比如'#if'、'#ifdef'、'#elif'、'#else'、'#endif'
處理'#include'預(yù)編譯指令,將被包含的文件插入到該預(yù)編譯指令的位置。注意,這個(gè)過(guò)程是遞歸進(jìn)行的,也就是說(shuō)被包含的文件可能還包含其他文件
刪除所有的注釋'//'和'/* */'
添加行號(hào)和文件名標(biāo)識(shí),比如 #2 'hello.c' 2,以便于編譯時(shí)編譯器產(chǎn)生調(diào)試用的行號(hào)信息及用于編譯時(shí)產(chǎn)生編譯錯(cuò)誤或警告時(shí)能夠顯示行號(hào)
保留所有的 #pragma 編譯器指令,因?yàn)榫幾g器需要使用它們
經(jīng)過(guò)預(yù)編譯后的 .i 文件不包含任何宏定義,因?yàn)樗械暮暌呀?jīng)被展開(kāi),并且包含的文件也已經(jīng)被插入到 .i 文件中。所以當(dāng)我們無(wú)法判斷宏定義是否正確或頭文件包含是否正確的時(shí)候,可以查看預(yù)編譯后的文件來(lái)確定問(wèn)題。
對(duì) hello.c 進(jìn)行預(yù)編譯:gcc -E hello.c -o hello .i
# 28 指的是文件 /usr/include/stdio.h 中的第 28 行,后面的是文件標(biāo)識(shí)
1.1 預(yù)處理指令
源代碼中的預(yù)處理指令叫做指示字(directive) ,從源代碼中可以輕易發(fā)現(xiàn),它們以井號(hào)(#)開(kāi)始,在每行都是第一個(gè)非空字符。而井號(hào)通常都在第一列,后面緊跟著指示字的關(guān)鍵字。
指示字 | 描述 |
#define | 定義宏名字,預(yù)處理程序會(huì)把這個(gè)宏擴(kuò)展到使用該名字的位置 |
#elif | 由#if 指示字提供一個(gè)用于計(jì)算的可選表達(dá)式 |
#else | 如果#if、#ifdef 或#ifndef 為假,提供一個(gè)用于編譯的可選代碼集合 |
#error | 產(chǎn)生出錯(cuò)消息,掛起預(yù)處理程序 |
#if | 如果計(jì)算算術(shù)表達(dá)式的結(jié)果為非零值,就編譯指示字和它匹配的#endif 之間的代碼 |
#ifdef | 如果已經(jīng)定義了指定的宏,就編譯指示字和它匹配的#endif 之間的代碼 |
#ifndef | 如果沒(méi)有定義指定的宏,就編譯指示字和它匹配的#endif 之間的代碼 |
#include | 查找指示字列表,直到找到指定的文件,然后將文件內(nèi)容插入,就好像在文本編輯器中插入一樣 |
#include_next | 和#include 一樣,但該指示字從找到當(dāng)前文件的目錄之后的目錄開(kāi)始查找 |
#line | 指出行號(hào)以及可能的文件名,報(bào)告給編譯程序,用于創(chuàng)建目標(biāo)文件中的調(diào)試信息 |
#pragma | 提供額外信息的標(biāo)準(zhǔn)方法,可用來(lái)指出一個(gè)編譯程序或一個(gè)平臺(tái) |
#undef | 刪除前面用#define 指示字創(chuàng)建的定義 |
#warning | 由預(yù)處理程序創(chuàng)建一個(gè)警告消息 |
## | 連接操作符,可用于宏內(nèi)將兩個(gè)字符串連接成一個(gè) |
1.1.1 #define
通過(guò)處理傳遞給宏的參數(shù)名字,加上井號(hào)(#)就可將其'字符串化'
可變的宏是具有可變數(shù)目參數(shù)的宏。這些參數(shù)由省略號(hào)代表,被保存在一個(gè)由逗號(hào)分隔的字符串中作為變量__VA_ARGS__,它會(huì)在宏的內(nèi)部進(jìn)行擴(kuò)展。例如,下面的宏接受任何數(shù)目的參數(shù):
可變的宏可以包含命名的參數(shù)(只要隨后有參數(shù)的變量長(zhǎng)度列表) 。例如,下面的宏有兩個(gè)固定參數(shù),以及一個(gè)變量列表:
前面所有形式的可變宏至少有一個(gè)參數(shù)需要滿(mǎn)足參數(shù)變量列表的需求,因?yàn)開(kāi)_VA_ARGS__前面是一個(gè)逗號(hào),它用于宏內(nèi)部的 fprintf()函數(shù)調(diào)用。作為連接操作符的一個(gè)特例,可以要求在__VA_ARGS__為空時(shí),將它插入變量列表可以去掉逗號(hào),如下:
1.1.2 #error 和 #warning
#error 指示字會(huì)引起預(yù)處理程序報(bào)告致命錯(cuò)誤或中斷。它可用來(lái)捕獲嘗試按照某種不可能工作的形式進(jìn)行編譯的條件。例如,下面的例子只有在定義了__unix__的情況下才能成功編譯:
#warning 指示字和#error 指示字的工作原理一樣
1.1.3 #include_next
#include_next 指示字只用于某些特殊情況。它用在頭文件內(nèi)部來(lái)包含其他頭文件,會(huì)令新頭文件的查找由找到當(dāng)前頭文件的目錄之后的目錄開(kāi)始
1.1.4 #line
調(diào)試器需要將文件名和行號(hào)與數(shù)據(jù)項(xiàng)和可執(zhí)行代碼關(guān)聯(lián)起來(lái),因此預(yù)處理程序會(huì)將這類(lèi)信息插入編譯程序的輸出結(jié)果。有必要按這種方式跟蹤原始名字和行號(hào),因?yàn)轭A(yù)處理程序會(huì)組合一些文件。編譯程序在編譯插入目標(biāo)代碼中的表時(shí),會(huì)使用這些數(shù)字。
通常,允許預(yù)處理程序通過(guò)計(jì)算來(lái)確定行號(hào),這正是需要的,但也有可能用其他一些處理來(lái)去掉這些行號(hào)。例如,實(shí)現(xiàn) SQL 語(yǔ)句的通常方法就是將它們寫(xiě)成宏,然后用特殊的處理器將這些宏擴(kuò)展成具體的 SQL 函數(shù)調(diào)用。這些擴(kuò)展可在很多行中運(yùn)行,這樣計(jì)算行號(hào)就很困難。SQL 處理會(huì)通過(guò)在輸出中插入#line 指示字進(jìn)行更正,這樣預(yù)處理程序就會(huì)跟蹤原始源代碼的行號(hào)。
可用于#line 指示字的特征和規(guī)則的列表:
為#line 指示字指定一個(gè)數(shù)字,會(huì)令預(yù)處理程序?qū)?dāng)前行號(hào)替換為指定行號(hào);指示字設(shè)置當(dāng)前行號(hào)為 137:#line 137
為#line 指示字指定行號(hào)和文件名,會(huì)令預(yù)處理程序改變行號(hào)以及當(dāng)前文件的名字。指示字會(huì)設(shè)置當(dāng)前位置為文件 muggles.h 的第一行:#line 1 'muggles.h'
#line 指示字修改預(yù)定義宏__LINE__ 和 __FILE__的內(nèi)容。
#line 指示字對(duì)由#include 指示字查找到的文件名或目錄沒(méi)有影響。
1.1.5 #pragma 和_Pragma
指示字#pragma 提供一種標(biāo)準(zhǔn)方法用來(lái)指定特定于編譯程序的信息。根據(jù)標(biāo)準(zhǔn),編譯程序可以附帶#pragma 指示字希望的任何意義。
所有 GCC pragma 都定義了兩個(gè)詞——第一個(gè)為 GCC,第二個(gè)為指定 pragma 的名字。
#pragma GCC dependency
dependency pragma 測(cè)試當(dāng)前文件的時(shí)間戳,對(duì)比其他文件的時(shí)間戳。如果其他文件更新,就會(huì)發(fā)出警告消息。測(cè)試文件 lexgen.tbl 的時(shí)間戳:
#pragma GCC dependency 'lexgen.tbl'
如果 lexgen.tbl 比當(dāng)前文件新,預(yù)處理程序就會(huì)產(chǎn)生如下消息:
warning: current file is older than 'lexgen.tbl'
可在 pragma 指示字中加入其他文本,它會(huì)作為警告消息的一部分,如下例所示:
#pragma GCC dependency 'lexgen.tbl' Header lex.h needs to be updated
它會(huì)創(chuàng)建下面的警告消息:
show.c:26: warning: current file is older than 'lexgen.tbl'
show.c:26: warning: Header lex.h needs to be updated
#pragma GCC poison
poison pragma 在每次使用指定名字的時(shí)候都會(huì)發(fā)出消息。例如,可用它確保從未調(diào)用指定函數(shù)。
下面的 pragma 在調(diào)用 memcpy 復(fù)本函數(shù)時(shí)就會(huì)發(fā)出警告消息:
#pragma GCC poison memcpy memmove
memcpy(target,source,size);
預(yù)處理程序會(huì)為該代碼產(chǎn)生如下警告消息:
show.c:38:9: attempt to use poisoned 'memcpy
#pragma GCC system_header
由 system_header pragma 打頭并隨后繼續(xù)到文件尾的代碼被看作是系統(tǒng)頭文件的一部分。編譯系統(tǒng)頭文件代碼有一些不同,因?yàn)檫\(yùn)行時(shí)庫(kù)不能被寫(xiě),因此它們是嚴(yán)格的純 C 標(biāo)準(zhǔn)格式。限制所有警告消息(除了#warnings 指示字) 。特殊情況下,一定的宏定義和擴(kuò)展不會(huì)發(fā)出警告消息。
_Pragma
通常的#pragma 指示字不能作為宏擴(kuò)展中的一部分包含進(jìn)來(lái),因此設(shè)計(jì)_Pragma 操作符是為了生成宏內(nèi)部的#pragma 指示字。為創(chuàng)建宏內(nèi)部的 poison pragma,代碼如下:_Pragma('GCC poison printf')
反斜線(xiàn)字符用作轉(zhuǎn)義字符,因此可用這種方式插入引用的字符串來(lái)創(chuàng)建 dependency
pragma:
_Pragma('GCC dependency 'lexgen.tbl'')
1.1.6 ##
可用于宏內(nèi)部將兩個(gè)源代碼權(quán)標(biāo)連接成一個(gè)的連接指示字。可用來(lái)構(gòu)造不會(huì)被解析器錯(cuò)誤解釋的名字。
1.2 預(yù)定義宏
GCC中包含了很多的預(yù)定義宏,常用的預(yù)定義宏如下:
宏 | 描述 |
__BASE_FILE__ | 引用的字符串,包含的是命令行中指定源文件的完整路徑名(不一定是使用宏的所有文件)。參見(jiàn)__FILE__ |
__CHAR_UNSIGNED__ | 定義該宏用來(lái)指出目標(biāo)機(jī)器的字符數(shù)據(jù)類(lèi)型是無(wú)符號(hào)的。limits.h中用它來(lái)確定CHAR_MIN和CHAR_MAX的值 |
__cplusplus | 只在C++程序中由定義。如果編譯程序不完全符合標(biāo)準(zhǔn),該宏定義為1,否則它會(huì)定義為標(biāo)準(zhǔn)的年和月,格式符合C中的__STDC_VERSION__ |
__DATA__ | 11個(gè)字符的引用字符串,包括編譯程序的日期。它的格式為'May 3 2017' |
__FILE__ | 引用字符串,包含使用宏的源文件名。參見(jiàn)__BASE_FILE__ |
__func__ | 同__FUNCTION__ |
__FUNCTION__ | 引用字符串,包含當(dāng)前函數(shù)的名字 |
__GNUC__ | 該宏總是定義為編譯程序的主要版本號(hào)。例如,如果編譯程序版本 |
__GNUC_MINOR__ | 該宏總是定義為編譯程序的次要版本號(hào)。例如,如果編譯程序版本 |
__GNUC_PATCHLEVEL__ | 該宏總是定義為編譯程序的修正版本號(hào)。例如,如果編譯程序版本 |
__GNUG__ | 由 C++編譯程序定義。無(wú)論何時(shí)定義了__cplusplus 和__GNUC__, |
__INCLUDE_LEVEL__ | 指出 include 文件當(dāng)前深度的整數(shù)值。該值在基本文件(命令行中指定的文件)時(shí)為 0,而每次#include 指示字輸入文件就會(huì)加 1 |
__LINE__ | 使用宏的文件的行號(hào) |
__NO_INLINE__ | 在沒(méi)有擴(kuò)展內(nèi)嵌函數(shù)的時(shí)候,該宏定義為 1,這可能因?yàn)闆](méi)有優(yōu)化或者不允許進(jìn)行內(nèi)嵌函數(shù) |
__OBJC__ | 如果程序被編譯成 Objective-C,該宏定義為 1 |
__OPTIMIZE__ | 無(wú)論何時(shí)只要指定任何級(jí)別的優(yōu)化處理,該宏就會(huì)定義為 1 |
__OPTIMIZE_SIZE__ | 如果設(shè)置進(jìn)行尺寸上的優(yōu)化而不是速度上的優(yōu)化,該宏就會(huì)定義為1 |
__REGISTER_PREFIX__ | 該宏為一個(gè)權(quán)標(biāo)(而不是字符串) ,它是注冊(cè)器名的前綴??捎脕?lái)編寫(xiě)能夠移植到多種環(huán)境中的匯編語(yǔ)言 |
__STDC__ | 定義為 1 指出該編譯程序符合標(biāo)準(zhǔn) C。 在編譯 C++和 Objective-C 時(shí)不定義該宏,而且在指定-traditional 選項(xiàng)的時(shí)候也不會(huì)定義該宏 |
__STDC_HOSTED__ | 定義為 1 指出'宿主'的環(huán)境(其中含有完整的標(biāo)準(zhǔn) C 庫(kù)) |
__STDC_VERSION__ | 長(zhǎng)整數(shù),指出標(biāo)準(zhǔn)版本號(hào),形式為它的年和月。例如,標(biāo)準(zhǔn)的 1999年修正版為 199901L。在編譯 C++和 Objective-C 時(shí)不會(huì)定義該宏,而且在指定-traditional 選項(xiàng)的時(shí)候也不會(huì)定義該宏 |
__STRICT_ANSI__ | 只有在命令行中指定-ansi 或-std 的時(shí)候,會(huì)定義該宏。在 GNU 頭文件中使用它來(lái)限制標(biāo)準(zhǔn)中的那些定義 |
__TIME__ | 引用 7 個(gè)字符的字符串,包含編譯程序的時(shí)間。格式為'18:10:34' |
__USER_LABEL_PREFIX__ | 該宏是一個(gè)權(quán)標(biāo)(而不是字符串) ,用作匯編語(yǔ)言中的符號(hào)前綴。該權(quán)標(biāo)依平臺(tái)有所變化,但它通常是個(gè)下劃線(xiàn)字符 |
__USING_SJLJ_EXCEPTIONS__ | 如果異常處理機(jī)制為 setjmp 和 longjmp,該宏定義為 1 |
__VERSION__ | 完整版本號(hào)。該信息沒(méi)有特殊格式,但它至少含有主要和次要版本號(hào) |
上一篇:GCC編譯器原理(三)------編譯原理三:編譯過(guò)程(2-1)---編譯之詞法分析
下一篇:GCC編譯器原理(二)------編譯原理一:ELF文件(3)
推薦閱讀最新更新時(shí)間:2025-06-27 03:44
設(shè)計(jì)資源 培訓(xùn) 開(kāi)發(fā)板 精華推薦
- 【下載】LAT1526 利用SPI的下溢實(shí)現(xiàn)回顯功能
- 【下載】LAT1509 STM32G0B1的FDCAN進(jìn)行通信丟包和多包案例分享
- 【下載】LAT1511 運(yùn)行Ux_Host_HUB_HID_MSC通過(guò)Hub連接U盤(pán)讀寫(xiě)不穩(wěn)定問(wèn)題分析
- 【下載】LAT1466 USB x Device HID Standalone的移植
- 【下載】LAT1488 STM32 USBxDevice MSC standalone移植示例
- 【下載】LAT1482 STM32G0單線(xiàn)串口通信幀錯(cuò)誤問(wèn)題解析
- Microchip 升級(jí)數(shù)字信號(hào)控制器(DSC)產(chǎn)品線(xiàn) 推出PWM 分辨率和 ADC 速度業(yè)界領(lǐng)先的新器件
- 意法半導(dǎo)體STM32MP23x:突破成本限制的工業(yè)AI應(yīng)用核心
- 意法半導(dǎo)體推出用于匹配遠(yuǎn)距離無(wú)線(xiàn)微控制器STM32WL33的集成的匹配濾波芯片
- ESP32開(kāi)發(fā)板連接TFT顯示屏ST7789跳坑記
- 如何讓ESP32支持analogWrite函數(shù)
- LGVL配合FreeType為可變字體設(shè)置字重-ESP32篇
- 使用樹(shù)莓派進(jìn)行 ESP32 Jtag 調(diào)試
- ESP32怎么在SPIFFS里面存儲(chǔ)html,css,js文件,以及網(wǎng)頁(yè)和arduino的通訊
- ESP32 freeRTOS使用測(cè)試
- LTC3772EDDB 演示板,550kHz 降壓型 DC/DC 轉(zhuǎn)換器,VIN = 2.75V-8V,VOUT = 3.3V @ 2A
- 用于大功率通用離線(xiàn)電源的 NCP1379 準(zhǔn)諧振電流模式控制器的典型應(yīng)用
- 用于 CRT 顯示器的 92W、-15V、5V、6.3V、15V、80V、200V AC 到 DC 多輸出電源
- 來(lái)自多個(gè)輸入源的 LTC3622EDE-23/5 雙路輸出降壓型穩(wěn)壓器的典型應(yīng)用電路
- AKD4644-B,帶有內(nèi)置 MIC/HP/RCV 放大器的 AK4644 立體聲編解碼器評(píng)估板
- 使用 Analog Devices 的 AD8033AKS 的參考設(shè)計(jì)
- LT6657BHMS8-1.25 負(fù)分流模式基準(zhǔn)的典型應(yīng)用電路
- CN0130
- 用于便攜式的 12 位 8 通道 DAC
- 使用 ROHM Semiconductor 的 BD12IA5WEFJ 的參考設(shè)計(jì)
- 為什么電源紋波測(cè)試結(jié)果與理論設(shè)計(jì)如此之大?
- UWB信標(biāo)與LoRa基站網(wǎng)關(guān)數(shù)據(jù)區(qū)別在哪里?
- 什么是AI 同傳技術(shù)
- 人工智能處理器和加速器到底是什么?
- PWM控制(脈沖寬度調(diào)制)的基本原理是什么
- 如何設(shè)計(jì)帶集成運(yùn)放構(gòu)成的方波發(fā)生器
- DSP調(diào)度算法的基本概念和原理
- 什么是集成動(dòng)力總成? 集成動(dòng)力總成的優(yōu)勢(shì)在哪里
- 鐵威馬 D5/D8 Hybrid:精準(zhǔn)擴(kuò)容時(shí)代的“5+8”雙引擎
- MOMAX摩米士堅(jiān)守品質(zhì)承諾,移動(dòng)電源符合國(guó)家 CCC 認(rèn)證及航空UN38.3安全標(biāo)準(zhǔn)
- 會(huì)寒暄 會(huì)引導(dǎo) 會(huì)記錄 智能外呼器人初體驗(yàn)
- AI服務(wù)以人為本,智齒智能外呼機(jī)器人全面升級(jí)
- 電銷(xiāo)機(jī)器人不夠“聰明”?可能是這三個(gè)原因,不可不知!
- 我國(guó)設(shè)計(jì)出一種微型機(jī)器人,將有望在人體內(nèi)運(yùn)輸細(xì)胞
- 閑談掃地機(jī)器人哪個(gè)品牌好
- msp430和stm32 dht11驅(qū)動(dòng)程序
- MSP430之仿真器驅(qū)動(dòng)安裝問(wèn)題
- msp430和stm32 lcd屏驅(qū)動(dòng)程序
- 【捕獲比較模式】msp430定時(shí)器比較捕獲理解
- 【MSP430】捕獲模式測(cè)量信號(hào)占空比