C語言條件編譯及編譯預處理階段

一、C語言由源代碼生成的各階段如下:

C源程序->編譯預處理->編譯->優化程序->匯編程序->鏈接程序->可執行文件

?????? 其中?編譯預處理階段,讀取c源程序,對其中的偽指令(以#開頭的指令)和特殊符號進行處理。或者說是掃描源代碼,對其進行初步的轉換,產生新的源代碼提供給編譯器。預處理過程先于編譯器對源代碼進行處理。

?????? 在C語言中,并沒有任何內在的機制來完成如下一些功能:在編譯時包含其他源文件、定義宏、根據條件決定編譯時是否包含某些代碼。要完成這些工作,就需要使用預處理程序。盡管在目前絕大多數編譯器都包含了預處理程序,但通常認為它們是獨立于編譯器的。預處理過程讀入源代碼,檢查包含預處理指令的語句和宏定義,并對源代碼進行響應的轉換。預處理過程還會刪除程序中的注釋和多余的空白字符。

二、偽指令(或預處理指令)定義

????? 預處理指令是以#號開頭的代碼行。#號必須是該行除了任何空白字符外的第一個字符。#后是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字符。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對源代碼做某些轉換。下面是部分預處理指令:
復制代碼
指令? 用途# 空指令,無任何效果#include 包含一個源代碼文件#define 定義宏#undef 取消已定義的宏#if 如果給定條件為真,則編譯下面代碼#ifdef 如果宏已經定義,則編譯下面代碼#ifndef 如果宏沒有定義,則編譯下面代碼#elif 如果前面的#if給定條件不為真,當前條件為真,則編譯下面代碼,其實就是else if的簡寫#endif 結束一個#if……#else條件編譯塊#error 停止編譯并顯示錯誤信息
復制代碼

三、預處理指令主要包括以下四個方面:

1、宏定義指令

????? 宏定義了一個代表特定內容的標識符。預處理過程會把源代碼中出現的宏標識符替換成宏定義時的值。宏最常見的用法是定義代表某個值的全局符號。宏的第二種用法是定義帶參數的宏(宏函數),這樣的宏可以象函數一樣被調用,但它是在調用語句處展開宏,并用調用時的實際參數來代替定義中的形式參數。

?

1.1 #define指令
1.1.1 #define預處理指令用來定義宏。該指令最簡單的格式是:聲明一個標識符,給出這個標識符代表的代碼(比如像圓周率這樣的數)。在后面的源代碼中,我們就可以使用定義的宏取代要使用的代碼,舉例如下:
//例1 #define MAX_NUM 10 int array[MAX_NUM]; for(i=0;i<MAX_NUM;i++)

???在這個例子中,對于閱讀該程序的人來說,符號MAX_NUM就有特定的含義,它代表的值給出了數組所能容納的最大元素數目。程序中可以多次使用這個值。作為一種約定,習慣上總是全部用大寫字母來定義宏,這樣易于把程序的宏標識符和一般變量標識符區別開來。如果想要改變數組的大小,只需要更改宏定義并重新編譯程序即可。

1.1.2 使用宏的好處有兩點:

一是使用方便。如下:

//例2 #define PAI 3.1415926
PAI顯然比3.1415926寫著方便。

二是定義的宏有了意義,可讀性強。如例1,MAX_NUM,望文生意便知是最大數量的意思,比單純使用10這個數字可讀性要強的多。

三是容易修改。如例1,如果在程序中有幾十次會使用到MAX_NUM,修改只需要在宏定義里面修改一次就可以,否則你會修改到崩潰。

1.1.3 宏表示的值可以是一個常量表達式,允許宏嵌套(必須在前面已定義)。例如:

//例3 #define ONE 1 #define TWO 2 #define SUM(ONE+TWO)

這里需要注意兩點:
一是注意上面的宏定義使用了括號。盡管它們并不是必須的。但出于謹慎考慮,還是應該加上括號的。例如:
??????????? six=THREE*TWO;
??? 預處理過程把上面的一行代碼轉換成:
??????????? six=(ONE+TWO)*TWO;
??? 如果沒有那個括號,就轉換成six=ONE+TWO*TWO;了。

也就是說預處理僅是簡單的字符替換,要時刻注意這一點,很多錯誤都會因此出現。

二是雖然我們舉例用了#define ONE 1 這個例子,但是一般要求宏定義要有其實際意義,#define ONE 1這種沒意義的宏定義是不推薦的。(大概是這么個意思,忘記具體怎么說了)

1.1.4 宏還可以代表一個字符串常量,例如:
??????????? #define VERSION "Version 1.0 Copyright(c) 2003"

1.2 帶參數的#define指令(宏函數)
??? 帶參數的宏和函數調用看起來有些相似。看一個例子:

//例4 #define Cube(x) (x)*(x)*(x)

??? 可以時任何數字表達式甚至函數調用來代替參數x。這里再次提醒大家注意括號的使用。宏展開后完全包含在一對括號中,而且參數也包含在括號中,這樣就保證了宏和參數的完整性。看一個用法:
//例4用法 int num=8+2; volume=Cube(num);

??? 展開后為(8+2)*(8+2)*(8+2);
??? 如果沒有那些括號就變為8+2*8+2*8+2了。
??? 下面的用法是不安全的:
??????????? volume=Cube(num++);
??? 如果Cube是一個函數,上面的寫法是可以理解的。但是,因為Cube是一個宏,所以會產生副作用。這里的書寫不是簡單的表達式,它們將產生意想不到的結果。它們展開后是這樣的:
??????????? volume=(num++)*(num++)*(num++);
??? 很顯然,結果是10*11*12,而不是10*10*10;
??? 那么怎樣安全的使用Cube宏呢?必須把可能產生副作用的操作移到宏調用的外面進行
??????????? int num=8+2;
??????????? volume=Cube(num);
??????????? num++;

宏函數使用不當會出現一些難以發現的錯誤,請慎重使用。


1.3 #運算符
??? 出現在宏定義中的#運算符把跟在其后的參數轉換成一個字符串。有時把這種用法的#稱為字符串化運算符。例如:

復制代碼
//例5 #define PASTE(n) "adhfkj"#n int main() {printf("%s\n",PASTE(15));return 0; } //輸出adhfj15
復制代碼

宏定義中的#運算符告訴預處理程序,把源代碼中任何傳遞給該宏的參數轉換成一個字符串。所以輸出應該是adhfkj15。


1.4 ##運算符(很少用)
??? ##運算符用于把參數連接到一起。預處理程序把出現在##兩側的參數合并成一個符號。看下面的例子:

復制代碼
//例6 #define NUM(a,b,c) a##b##c #define STR(a,b,c) a##b##c int main(){printf("%d\n",NUM(1,2,3));printf("%s\n",STR("aa","bb","cc"));return 0;} //最后程序的輸出為: 123 aabbcc
復制代碼

?

2、條件編譯指令。

????? 程序員可以通過定義不同的宏來決定編譯程序對哪些代碼進行處理。條件編譯指令將決定那些代碼被編譯,而哪些是不被編譯的。可以根據表達式的值或者某個特定的宏是否被定義來確定編譯條件。

2.1 #if/#endif/#else/#elif指令
??? #if指令檢測跟在制造另關鍵字后的常量表達式。如果表達式為真,則編譯后面的代碼,知道出現#else、#elif或#endif為止;否則就不編譯。
??? #endif用于終止#if預處理指令。
??? #else指令用于某個#if指令之后,當前面的#if指令的條件不為真時,就編譯#else后面的代碼。

復制代碼
//例7 #define DEBUG //此時#ifdef DEBUG為真 //#define DEBUG 0 //此時為假 int main() {#ifdef DEBUGprintf("Debugging\n"); ?? #elseprintf("Not debugging\n"); ?? #endifprintf("Running\n"); ?? return 0; }
復制代碼

這樣我們就可以實現debug功能,每次要輸出調試信息前,只需要#ifdef DEBUG判斷一次。不需要了就在文件開始定義#define DEBUG 0

#elif預處理指令綜合了#else和#if指令的作用。

復制代碼
//例8 #define TWO int main() {#ifdef ONEprintf("1\n");#elif defined TWOprintf("2\n");#elseprintf("3\n");#endif } //輸出結果是2。
復制代碼

2.2 #ifdef和#ifndef

這二者主要用于防止重復包含。我們一般在.h頭文件前面加上這么一段:

復制代碼
//頭文件防止重復包含 //funcA.h #ifndef FUNCA_H #define FUNCA_H //頭文件內容 #end if
復制代碼

這樣,如果a.h包含了funcA.h,b.h包含了a.h、funcA.h,重復包含,會出現一些type redefination之類的錯誤。
#if defined等價于#ifdef; #if !defined等價于#ifndef

3、頭文件包含指令。

采用頭文件的目的主要是為了使某些定義可以供多個不同的C源程序使用。因為在需要用到這些定義的C源程序中,只需加上一條#include語句即可,而不必再在此文件中將這些定義重復一遍。預編譯程序將把頭文件中的定義統統都加入到它所產生的輸出文件中,以供編譯程序對之進行處理。

? #include預處理指令的作用是在指令處展開被包含的文件。包含可以是多重的,也就是說一個被包含的文件中還可以包含其他文件。標準C編譯器至少支持八重嵌套包含。預處理過程不檢查在轉換單元中是否已經包含了某個文件并阻止對它的多次包含,這個的處理辦法上面已經給出。

? 在程序中包含頭文件有兩種格式:
??????? #include <my.h>
??????? #include "my.h"
???第一種方法是用尖括號把頭文件括起來。這種格式告訴預處理程序在編譯器自帶的或外部庫的頭文件中搜索被包含的頭文件。第二種方法是用雙引號把頭文件括起來。這種格式告訴預處理程序在當前被編譯的應用程序的源代碼文件中搜索被包含的頭文件,如果找不到,再搜索編譯器自帶的頭文件。
???采用兩種不同包含格式的理由在于,編譯器是安裝在公共子目錄下的,而被編譯的應用程序是在它們自己的私有子目錄下的。一個應用程序既包含編譯器提供的公共頭文件,也包含自定義的私有頭文件。采用兩種不同的包含格式使得編譯器能夠在很多頭文件中區別出一組公共的頭文件。

4、特殊符號。

預編譯程序可以識別一些特殊的符號。預編譯程序對于在源程序中出現的這些串將用合適的值進行替換。

4.1 __LINE__

注意,是雙下劃線,而不是單下劃線 。
__FILE__ 包含當前程序文件名的字符串
__LINE__  表示當前行號的整數
__DATE__ 包含當前日期的字符串
__STDC__  如果編譯器遵循ANSI C標準,它就是個非零值
__TIME__ 包含當前時間的字符串
復制代碼
//9 #include<stdio.h> int main() {printf("Hello World!\n");printf("%s\n",__FILE__);printf("%d\n",__LINE__); ?? return 0; }
復制代碼

4.2 #line等

#error指令將使編譯器顯示一條錯誤信息,然后停止編譯。
#line指令改變_LINE_與_FILE_的內容,它們是在編譯程序中預先定義的標識符。
#pragma指令沒有正式的定義。編譯器可以自定義其用途。典型的用法是禁止或允許某些煩人的警告信息。

復制代碼
//例10,#line舉例 #line 100????????? //初始化行計數器 #include<stdio.h> //行號100 int main() {printf("Hello World!\n");printf("%d",__LINE__);return 0; } //輸出104
復制代碼

四、預編譯程序所完成的基本上是對源程序的“替代”工作。經過此種替代,生成一個沒有宏定義、沒有條件編譯指令、沒有特殊符號的輸出文件。這個文件的含義同沒有經過預處理的源文件是相同的,但內容有所不同。下一步,此輸出文件將作為編譯程序的輸出而被翻譯成為機器指令。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/255304.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/255304.shtml
英文地址,請注明出處:http://en.pswp.cn/news/255304.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HALCON示例程序board.hdev檢測電路板焊錫有無程序剖析

HALCON示例程序board.hdev檢測電路板焊錫有無程序剖析 示例程序源碼&#xff08;加注釋&#xff09; *這是關于系統設置的函數&#xff0c;剪輯區域&#xff0c;設置剪輯區域設置為使能。為clip_region做的設置&#xff0c;后文會介紹 get_system (‘clip_region’, Informat…

【機器學習】SVM理論與python實踐系列

理論部分: 《機器學習——支持向量機SVM之線性模型》 《機器學習——支持向量機SVM之非線性模型低維到高維映射》 《機器學習——支持向量機SVM之非線性模型原問題與對偶問題》 《機器學習——常用核函數》 《機器學習——支持向量機SVM之非線性模型原問題轉化為對偶問題…

eoLinker-API_Shop_驗證碼識別與生成類API調用的代碼示例合集:六位圖片驗證碼生成、四位圖片驗證碼生成、簡單驗證碼識別等...

以下示例代碼適用于 www.apishop.net 網站下的API&#xff0c;使用本文提及的接口調用代碼示例前&#xff0c;您需要先申請相應的API服務。 六位圖片驗證碼生成&#xff1a;包括純數字、小寫字母、大寫字母、大小寫混合、數字小寫、數字大寫、數字大小寫等情況。四位圖片驗證碼…

網上書店 買方數據庫

買方表 屬性 字段名 類型 鍵值 是否空 用戶ID UserId char(5) 主鍵 用戶名稱 UserName nvarchar(50) 用戶密碼 UserPwd nvarchar(50) 用戶真實姓名 UserRealName nvarchar(50) 用戶地址 UserAddress nvarchar(100) …

Web開發模式(MVC設計模式)

1.MVC&#xff1a;(Model-View-Controller)操作流程 顯示層View:主要負責接收Servlet傳遞的內容&#xff0c;并調用JavaBean把內容顯示給用戶。 控制層Controller:負責所有的用戶請求參數&#xff0c;判斷請求參數是否合法&#xff0c;根據請求方式調用JavaBean進行處理&#x…

Arduino IDE 配置文件

最近學習Arduino。 Arduino開源硬件和Arduino IDE是一個很容易上手的系統。 目前arduino已經支持很多種板類型&#xff0c;甚至已經支持了部分arm芯片。比如arduino ng、arduino uno、arduino mini、pro mini等。但是大多數情況&#xff0c;都是使用的atmega8/at…

HALCON示例程序bottle.hdev、bottlet.hdev瓶體字符OCR的訓練和檢測

HALCON示例程序bottle.hdev、bottlet.hdev瓶體字符OCR的訓練和檢測 示例程序源碼&#xff08;加注釋&#xff09; 1、先介紹bottlet.hdev&#xff08;訓練OCR識別文件&#xff09; *定義一個字符串變量FontName &#xff0c;內容是bottle FontName : ‘bottle’ *第一步分割…

【機器學習】神經網絡BP理論與python實例系列

理論部分 《機器學習——人工神經網絡之發展歷史&#xff08;神經元數學模型、感知器算法&#xff09;》 《機器學習——人工神經網絡之多層神經網絡&#xff08;多層與三層&#xff09;》 《機器學習——人工神經網絡之后向傳播算法&#xff08;BP算法&#xff09;》 《機…

bzoj1597: [Usaco2008 Mar]土地購買

斜率優化dp。 #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn 50000 10;struct Field {long long x,y; } t[maxn]; long long f[maxn],x[maxn],y[maxn]; int q[maxn],l,r; int n,cnt;bool cmp(F…

vue封裝axios接口

一、安裝axios axios安裝命令&#xff1a;cnpm install axios 二、在文件中引用axios 一開始我是放在src下的main.js這個文件里面&#xff0c;后來發現mounted鉤子讀取接口方法為undefined&#xff0c;百度了才發現是vue生命周期的原因&#xff0c;最好的解決辦法是把axios單獨…

編寫Arduino支持的C++類庫

以下為摘抄的例子&#xff0c;已經親自驗證過&#xff0c;例子是正確的 我們在上一講中實現了一個TN901紅外溫度傳感器51程序到Arduino程序的轉換&#xff0c;如果代碼越來越多這樣程序的可維護性會隨之降低&#xff0c;也不適合團度開發。我們應該把常用的文件封裝成C庫&#…

函數,游標與存儲過程的綜合應用

--在TOY數據庫中完成以下操作use toygo--1、編寫一個存儲過程&#xff0c;接收任意一個訂單號&#xff0c;打印訂單的表頭數據。格式如下&#xff1a;--(其中訂單編號由訂單日期訂單號的字符串組成)--訂單編號:20010520000001 訂貨日期:2001-05-20 訂貨人:拉爾森create pro…

HALCON示例程序check_blister.hdev藥品膠囊檢測

HALCON check_blister.hdev藥品膠囊檢測 示例程序源碼&#xff08;加注釋&#xff09; 顯示、讀入圖片、設置顯示字體等&#xff0c;之前的帖子已經介紹過了 dev_close_window () dev_update_off () read_image (ImageOrig, ‘blister/blister_reference’) dev_open_window_f…

【機器學習實戰】——常見函數積累

目錄 第二章 k近鄰算法 1、array.sum(axies 1) : 2、array.argsort(axies0/1) 3、array.tile(mat,(m,n)) 4、dict.get(key,value) 5、sorted函數 6、string.strip()函數 7、string.split() 8、scatter&#xff08;&#xff09;函數 9、min()&max() 10、enumera…

安裝oracle 11g 客戶端,檢查過程中報物理內存不足的解決

今早接到同事電話&#xff0c;說安裝oracle 11g客戶端的時候&#xff0c;在檢查先決條件的時候&#xff0c;報錯&#xff0c;說內存不足&#xff0c;但是本機的內存是2G&#xff0c;肯定夠用&#xff1a;如圖&#xff1a; 找了一圈&#xff0c;原來Oracle執行先決條件檢查是依賴…

智能時代 軟件賦能——2017中國軟件技術大會

由中國科學院軟件研究所、中科軟科技股份有限公司聯合主辦&#xff0c;北京中科凱亞科技有限公司協辦的第15屆中國軟件技術大會將于2017年12月在北京 國家會議中心隆重舉行。 毋庸置疑&#xff0c;我們正在邁入智能時代&#xff0c;伴隨著物聯網、云計算、大數據、人工智能等智…

Arduino 代碼機制

新建一個Arduino程序。可是建完就郁悶了&#xff0c;因為只看到了setup和loop函數&#xff0c;卻沒有基本的c函數。 void setup() {// put your setup code here, to run once: }void loop() {// put your main code here, to run repeatedly: }于是好奇心就來了&#xff0c;當…

Codeforces Round #401 (Div. 2) D. Cloud of Hashtags

題目鏈接&#xff1a;D. Cloud of Hashtags 題意&#xff1a; 給你n個字符串&#xff0c;讓你刪后綴&#xff0c;使得這些字符串按字典序排列&#xff0c;要求是刪除的后綴最少 題解&#xff1a; 由于n比較大&#xff0c;我們可以將全部的字符串存在一個數組里面&#xff0c;然…

HALCON示例程序check_blister_mixed.hedv藥品膠囊缺陷檢測

HALCON示例程序check_blister_mixed.hedv藥品膠囊缺陷檢測 示例程序源碼&#xff08;加注釋&#xff09; 讀入圖片與顯示相關設置 dev_close_window () read_image (Image, ‘blister/blister_mixed_reference’) dev_open_window_fit_image (Image, 0, 0, -1, -1, WindowHan…

php類與對象

1.類與對象 對象&#xff1a;實際存在該類事物中每個實物的個體。$a new User(); 實例化后的$a 引用&#xff1a;php的別名&#xff0c;兩個不同的變量名字指向相同的內容 封裝: 把對象的屬性和方法組織在一個類&#xff08;邏輯單元&#xff09;里 繼承&#xff1a;以原有的類…