c語言編譯預處理和條件編譯執行過程的理解
在C語言的程式中可包括各種以符號#開頭的編譯指令,這些指令稱為預處理命令。預處理命令屬於C語言編譯器,而不是C語言的組成部分。通過預處理命令可擴充套件C語言程式設計的環境。
一.預處理的工作方式
1.1.預處理的功能
在整合開發環境中,編譯,連結是同時完成的。其實,C語言編譯器在對原始碼編譯之前,還需要進一步的處理:預編譯。
所以,完整的步驟是:預編譯 -> 編譯 -> 連結
預編譯的主要作用如下:
1.將原始檔中以”include”格式包含的檔案複製到編譯的原始檔中。 2.用實際值替換用“#define”定義的字串。 3.根據“#if”後面的條件決定需要編譯的程式碼。
1.2預處理的工作方式
預處理的行為是由指令控制的。這些指令是由#字元開頭的一些命令。
#define指令定義了一個巨集---用來代表其他東西的一個命令,通常是某一個型別的常量。預處理會通過將巨集的名字和它的定義儲存在一起來響應#define指令。當這個巨集在後面的程式中使用到時,前處理器”擴充套件”了巨集,將巨集替換為它所定義的值。例如:下面這行命令:
#define PI 3.141592654
#include指令告訴前處理器開啟一個特定的檔案,將它的內容作為正在編譯的檔案的一部分“包含”進來。例如:下面這行命令:
#include<stdio.h>
指示前處理器開啟一個名字為stdio.h的檔案,並將它的內容加到當前的程式中。
前處理器的輸入是一個C語言程式,程式可能包含指令。前處理器會執行這些指令,並在處理過程中刪除這些指令。前處理器的輸出是另外一個程式:原程式的一個編輯後的版本,不再包含指令。前處理器的輸出被直接交給編譯器,編譯器檢查程式是否有錯誤,並經程式翻譯為目的碼。
二.預處理指令
2.1.預處理指令
大多數前處理器指令屬於下面3種類型:
巨集定義:#define 指令定義一個巨集,#undef指令刪除一個巨集定義。 檔案包含:#include指令導致一個指定檔案的內容被包含到程式中。 條件編譯:#if,#ifdef,#ifndef,#elif,#else和#endif指令可以根據編譯器可以測試的條件來將一段文字包含到程式中或排除在程式之外。
剩下的#error,#line和#pragma指令更特殊的指令,較少用到。
2.2.指令規則
指令都是以#開始。#符號不需要在一行的行首,只要她之前有空白字元就行。在#後是指令名,接著是指令所需要的其他資訊。 在指令的符號之間可以插入任意數量的空格或橫向製表符。 指令總是第一個換行符處結束,除非明確地指明要繼續。 指令可以出現在程式中任何地方。我們通常將#define和#include指令放在檔案的開始,其他指令則放在後面,甚至在函式定義的中間。 註釋可以與指令放在同一行。
三.巨集定義命令----#define
使用#define命令並不是真正的定義符號常量,而是定義一個可以替換的巨集。被定義為巨集的標示符稱為“巨集名”。在編譯預處理過程時,對程式中所有出現的“巨集名”,都用巨集定義中的字串去代換,這稱為“巨集代換”或“巨集展開”。
在C語言中,巨集分為有引數和無引數兩種。
3.1.無引數的巨集
其定義格式如下:
#define 巨集名 字串
在以上巨集定義語句中,各部分的含義如下:
#:表示這是一條預處理命令(凡是以“#”開始的均為預處理命令)。 define:關鍵字“define”為巨集定義命令。 巨集名:是一個標示符,必須符合C語言標示符的規定,一般以大寫字母標示巨集名。 字串:可以是常數,表示式,格式串等。在前面使用的符號常量的定義就是一個無引數巨集定義。
Notice:預處理命令語句後面一般不會新增分號,如果在#define最後有分號,在巨集替換時分號也將替換到原始碼中去。在巨集名和字串之間可以有任意個空格。
#define PI 3.14
在使用巨集定義時,還需要注意以下幾點:
巨集定義是巨集名來表示一個字串,在巨集展開時又以該字串取代巨集名。這只是一種簡單的代換,字串中可以含任何字元,可以是常數,也可以是表示式,預處理程式對它不作任何檢查。如有錯誤,只能在編譯已被巨集展開後的源程式時發現。 巨集定義必須寫在函式之外,其作用域為巨集定義命令起到源程式結束。 巨集名在源程式中若用引號括起來,則預處理程式不對其作巨集替換。 巨集定義允許巢狀,在巨集定義的字串中可以使用已經定義的巨集名。在巨集展開時由預處理程式層層替換。 習慣上巨集名可用大寫字母表示,以方便與變數區別。但也允許用小寫字母。
3.2帶引數的巨集
#define命令定義巨集時,還可以為巨集設定引數。與函式中的引數類似,在巨集定於中的引數為形式引數,在巨集呼叫中的引數稱為實際引數。對帶引數的巨集,在呼叫中,不僅要巨集展開,還要用實參去代換形參。
帶參巨集定義的一般形式為:
#define 巨集名(形參表) 字串
在定義帶引數的巨集時,巨集名和形參表之間不能有空格出現,否則,就將巨集定義成為無引數形式,而導致程式出錯。
#define ABS(x) (x)<0?-(x):(x)
以上的巨集定義中,如果x的值小於0,則使用一元運算子(-)對其取負,得到正數。
帶參的巨集和帶參的函式相似,但其本質是不同的。使用帶參巨集時,在預處理時將程式原始碼替換到相應的位置,編譯時得到完整的目的碼,而不進行函式呼叫,因此程式執行效率要高些。而函式呼叫只需要編譯一次函式,程式碼量較少,一般情況下,對於簡單的功能,可使用巨集替換的形式來使用。
3.3.預處理操作符#和##
3.3.1.操作符#
在使用#define定義巨集時,可使用操作符#在字串中輸出實參。Eg:
#define AREA(x,y) printf(“長為“#x”,寬為“#y”的長方形的面積:%d\n”,(x)*(y));
3.3.2.操作符##
與操作符#類似,操作符##也可用在帶參巨集中替換部分內容。該操作符將巨集中的兩個部分連線成一個內容。例如,定義如下巨集:
#define VAR(n) v##n
當使用一下方式引用巨集:
VAR(1)
預處理時,將得到以下形式:
V1
如果使用以下巨集定義:
#define FUNC(n) oper##n
當實參為1時,預處理後得到一下形式:
oper1
四.檔案包含------include
當一個C語言程式由多個檔案模組組成時,主模組中一般包含main函式和一些當前程式專用的函式。程式從main函式開始執行,在執行過程中,可呼叫當前檔案中的函式,也可呼叫其他檔案模組中的函式。
如果在模組中要呼叫其他檔案模組中的函式,首先必須在主模組中宣告該函式原型。一般都是採用檔案包含的方法,包含其他檔案模組的標頭檔案。
檔案包含中指定的檔名即可以用引號括起來,也可以用尖括號括起來,格式如下:
#include< 檔名> 或 #include“檔名”
如果使用尖括號<>括起檔名,則編譯程式將到C語言開發環境中設定好的 include檔案中去找指定的檔案。
因為C語言的標準標頭檔案都存放在include資料夾中,所以一般對標準標頭檔案採用尖括號;對程式設計自己編寫的檔案,則使用雙引號。
如果自己編寫的檔案不是存放在當前工作資料夾,可以在#include命令後面加在路徑。
#include命令的作用是把指定的檔案模組內容插入到#include所在的位置,當程式編譯連結時,系統會把所有#include指定的檔案連結生成可執行程式碼。檔案包含必須以#開頭,表示這是編譯預處理命令,行尾不能用分號結束。
#include所包含的檔案,其副檔名可以是“.c”,表示包含普通C語言源程式。也可以是 “.h”,表示C語言程式的標頭檔案。C語言系統中大量的定義與宣告是以標頭檔案形式提供的。 “.h”是介面檔案,如果想理解C語言介面的寫法,有必要琢磨一下 “.h”。
通過#define包含進來的檔案模組中還可以再包含其他檔案,這種用法稱為巢狀包含。巢狀的層數與具體C語言系統有關,但是一般可以巢狀8層以上。
五.條件編譯
前處理器還提供了條件編譯功能。在預處理時,按照不同的條件去編譯程式的不同部分,從而得到不同的目的碼。
使用條件編譯,可方便地處理程式的除錯版本和正式版本,也可使用條件編譯使程式的移植更方便。
5.1使用#if
與C語言的條件分支語句類似,在預處理時,也可以使用分支,根據不同的情況編譯不同的原始碼段。
#if 的使用格式如下:
#if 常量表達式 程式段 #else 程式段 #endif
該條件編譯命令的執行過程為:若常量表達式的值為真(非0),則對程式段1進行編譯,否則對程式段2進行編譯。因此可以使程式在不同條件下完成不同的功能。
舉個例子:
#define DEBUG 1 int main() { int i,j; char ch[26]; for(i='a';j=0;i<='z';i++,j++) { ch[j]=i; #if DEBUG printf("ch[%d]=%c\n",j,ch[j]); #endif } for(j=0;j<26;j++) { printf("%c",ch[j]); } return 0; }
#if預編譯命令還可使用多分支語句格式,具體格式如下:
#if 常量表達式 1 程式段 1 #elif 常量表達式 2 程式段 2 … … #elif 常量表達式 n 程式段 n #else 程式段 m #endif
關鍵字#elif與多分支if語句中的else if類似。
舉個例子
#define os win #if os=win #include"win.h" #elif os=linux #include"linux.h" #elif os=mac #include"mac.h" #endif
#if和#elif還可以進行巢狀,C89標準中,巢狀深度可以到達8層,而C99允許巢狀達到63層。在巢狀時,每個#endif,#else或#elif與最近的#if或#elif配對。
Eg:
#define MAX 100 #define OLD -1 int main() { int i; #if MAX>50 { #if OLD>3 { i=1; { #elif OLD>0 { i=2; } #else { i=3; } #endif } #else { #if OLD>3 { i=4; } #elif OLD>4 { i=5; } #else { i=6; } #endif } #endif return 0; }
5.2使用#ifdef和#ifndef
在上面的#if條件編譯命令中,需要判斷符號常量定義的具體值。在很多情況下,其實不需要判斷符號常量的值,只需要判斷是否定義了該符號常量。這時,可不使用#if命令,而使用另外一個預編譯命令———#ifdef.
#ifdef命令的使用格式如下: #ifdef 識別符號 程式段 1 #else 程式段 2 #endif
其意義是,如果#ifdef後面的識別符號已被定義過,則對“程式段1”進行編譯;如果沒有定義識別符號,則編譯“程式段2”。一般不使用#else及後面的“程式2”。
而#ifndef的意義與#ifdef相反,其格式如下:
#ifndef 識別符號 程式段 1 #else 程式段 2 #endif
其意義是:如果未定義識別符號,則編譯“程式段1”;否則編譯“程式段2”。
5.3使用#defined和#undef
與#ifdef類似的,可以在#if命令中使用define來判斷是否已定義指定的識別符號。例如:
#if defined 識別符號 程式段 1 #endif
與下面的標示方式意義相同。
#ifdef 識別符號 程式段 1 #endif
也可使用邏輯運算子,對defined取反。例如:
#if ! define 識別符號 程式段 1 #endif
與下面的標示方式意義相同。
#ifndef 識別符號 程式段 1 #endif
在#ifdef和#ifndef命令後面的識別符號是使用#define進行定義的。在程式中,還可以使用#undef取消對識別符號的定義,其形式為:
#undef 識別符號
舉個例子:
#define MAX 100 …… #undef MAX
在以上程式碼中,首先使用#define定義識別符號MAX,經過一段程式程式碼後,又可以使用#undef取消已定義的識別符號。使用#undef命令後,再使用#ifdef max,將不會編譯後的原始碼,因為此時識別符號MAX已經被取消定義了。
六.其他預處理命令
6.1.預定義的巨集名
ANSI C標準預定義了五個巨集名,每個巨集名的前後均有兩個下畫線,避免與程式設計師定義相同的巨集名(一般都不會定義前後有兩個下劃線的巨集)。這5個巨集名如下:
__DATE__:當前源程式的建立日期。 __FILE__:當前源程式的檔名稱(包括碟符和路徑)。 __LINE__:當前被編譯程式碼的行號。 __STDC__:返回編譯器是否位標準C,若其值為1表示符合標準C,否則不是標準C. __TIME__:當前源程式的建立時間。
舉個例子:
#include<stdio.h> int main() { int j; printf("日期:%s\n",__DATE__); printf("時間:%s\n",__TIME__}; printf("檔名:%s\n",__FILE__); printf("這是第%d行程式碼\n",__LINE__); printf("本編譯器%s標準C\n",(__STD__)?"符合":"不符合"); return 0; }
6.2.重置行號和檔名命令------------#line
使用__LINE__預定義巨集名賑災編譯的程式行號。使用#line命令可改變預定義巨集__LINE__與__FILE__的內容,該命令的基本形如下:
#line number[“filename”]
其中的數字為一個正整數,可選的檔名為有效檔案識別符號。行號為原始碼中當前行號,檔名為原始檔的名字。命令為#line主要用於除錯以及其他特殊應用。
舉個例子:
1:#include<stdio.h> 2:#include<stdlib.h> 4:#line 1000 6:int main() 7:{ 8: printf("當前行號:%d\n",__LINE__); 9: return 0; 10:}
在以上程式中,在第4行中使用#line定義的行號為從1000開始(不包括#line這行)。所以第5行的編號將為1000,第6行為1001,第7行為1002,第8行為1003.
6.3.修改編譯器設定命令 ------------#pragma
#pragma命令的作用是設定編譯器的狀態,或者指示編譯器完全一些特定的動作。#pragma命令對每個編譯器給出了一個方法,在保持與C語言完全相容的情況下,給出主機或者作業系統專有的特徵。其格式一般為:
#pragma Para
其中,Para為引數,可使用的引數很多,下面列出常用的引數:
Message引數,該引數能夠在編譯資訊輸出視窗中輸出對應的資訊,這對於原始碼資訊的控制是非常重要的,其使用方法是:
#pragma message(訊息文字)
當編譯器遇到這條指令時,就在編譯輸出視窗中將訊息文字顯示出來。
另外一個使用比較多得pragma引數是code_seg.格式如:
#pragma code_seg([“section_name”[,section_class]])
它能夠設定程式中函式程式碼存放的程式碼段,在開發驅動程式的時候就會使用到它。
引數once,可保證標頭檔案被編譯一次,其格式為:
#pragma once
只要在標頭檔案的最開始加入這條指令就能夠保證標頭檔案被編譯一次。
6.4.產生錯誤資訊命令 ------------#error
#error命令強制編譯器停止編譯,並輸出一個錯誤資訊,主要用於程式除錯。其使用如下:
#error 資訊錯誤
注意,錯誤資訊不用雙括號括起來。當遇到#error命令時,錯誤資訊將顯示出來。
例如,以下編譯前處理器命令判斷預定義巨集__STDC__,如果其值不為1,則顯示一個錯誤資訊,提示程式設計師該編譯器不支援ANSI C標準。
#if __STDC__!=1 #error NOT ANSI C #endif
七.行內函數
在使用#define定義帶引數巨集時,在呼叫函式時,一般需要增加系統的開銷,如引數傳遞,跳轉控制,返回結果等額外操作需要系統記憶體和執行時間。而使用帶引數巨集時,通過巨集替換可再編譯前將函式程式碼展開導原始碼中,使編譯後的目標檔案含有多段重複的程式碼。這樣做,會增加程式的程式碼量,都可以減少執行時間。
在C99標準鍾,還提供另外一種解決方法:使用行內函數。
在程式編譯時,編譯器將程式中出現的行內函數的呼叫表示式用行內函數的函式體來進行替代。顯然,這種做法不會產生轉去轉回得問題。都是由於在編譯時將函式體中的程式碼被替代到程式中,因此會增加目的碼量,進而增加空間的開銷,而在時間開銷上不像函式呼叫時那麼大,可見它是以增加目的碼為程式碼來換取時間的節省。
定義行內函數的方法很簡單,只要在定義函式頭的前面加上關鍵字inline即可。行內函數的定義與一般函式一樣。例如,定於一個兩個整數相加的函式:
#include<stdio.h> #include<stdlib.h> inline int add(int x,int y); inline int add(int x,int y) { return x+y; } int main() { int i,j,k; printf("請輸入兩個整數的值:\n"); scanf("%d %d",&i,&j); k=add(i,j); printf("k=%d\n",k); return 0; }
在程式中,呼叫函式add時,該函式在編譯時會將以上程式碼複製過來,而不是像一般函式那樣是執行時被呼叫。
行內函數具有一般函式的特性,它與一般函式所不同之處在於函式呼叫的處理。一般函式進行呼叫時,要講程式執行權轉導被調函式中,然後再返回到呼叫到它的函式中;而行內函數在呼叫時,是將呼叫表示式用行內函數體來替換。在使用行內函數時,應該注意如下幾點:
在行內函數內部允許用迴圈語句和開關語句。但是,程式在經過編譯之後,這個函式是不會作為行內函數進行呼叫的。 行內函數的定義必須出現在行內函數第一次被呼叫之前。
其實,在程式中宣告一個函式為內聯時,編譯以後這個函式不一定是內聯的,
即程式只是建議編譯器使用行內函數,但是編譯器會根據函式情況決定是否使用內聯,所以如果編寫的行內函數中出現迴圈或者開關語句,程式也不會提示出錯,但那個函式已經不是內聯函數了。
一般都是將一個小型函式作為行內函數。
相關推薦
c語言編譯預處理和條件編譯執行過程的理解
在C語言的程式中可包括各種以符號#開頭的編譯指令,這些指令稱為預處理命令。預處理命令屬於C語言編譯器,而不是C語言的組成部分。通過預處理命令可擴充套件C語言程式設計的環境。 一.預處理的工作方式 1.1.預處理的功能 在整合開發環境中,編譯,連結是同時完成的。其實,C語言編譯器在對原始碼編譯之前
C/C++ 程式的編譯預處理和條件編譯指令詳解
** C/C++ 程式的編譯預處理和條件編譯指令詳解** 編譯預處理 (1)#include 包含指令作用為 將一個原始檔嵌入到當前原始檔中該點處。 #include<檔名> : 按標準方式搜尋,檔案位於C++系統目錄的include子目錄下 #include"檔名" :
C語言的預處理之"條件編譯"
C語言的預處理主要有三個方面的內容: 巨集定義 檔案包含 條件編譯 預處理命令以符號"#"開頭。 採用條件編譯,可以減少被編譯的語句,從而減少目標的長度。當條件編譯段比較多時,目標程式長度可以大大減少。 條件編譯主要包括: #if
C語言__預處理(巨集定義、檔案包含、條件編譯)
C語言__預處理(巨集定義、檔案包含、條件編譯) 預處理簡單理解 1.C語言在對源程式進行編譯之前,會先對一些特殊的預處理指令作解釋(比如之前使用的#include檔案包含指令),產生一個新的源程式(這個過程稱為編譯預處理),之後再進行通常的編譯
C語言的預處理、編譯、彙編、連結
一、預處理 預處理指令的執行主要包含下列事情: 1.標頭檔案的包含 2.註釋的刪除 3.巨集定義的替換 4.條件編譯的選擇 指令:gcc -E test.c -o test.i /* 呼叫的是前處理器c
【Object-c基礎】預定義,條件編譯,陣列
1. 預定義:#define 在object-c中,跟C語言一樣都是採用#define才使用,但末尾是沒有分號的; 例子: #define PI 3.14 在之後即可引用,這點在iphone開發中一定每個組建的tag非常好用,並且可以集合放一個定義檔案中。 2. 條件編譯:#ifdef,
c語言學習--巨集定義、條件編譯等
1. 防止一個頭檔案被重複包含 #ifndef COMDEF_H #define COMDEF_H //標頭檔案內容 #endif 2. 重新定義一些型別,防止由於各種平臺和編譯器的不同,而產生的型別位元組數差異,方便移
C語言之運算符和條件結構
比較運算 第三名 user 石頭 年齡 pan 註意 break -1 表達式:是有操作數和運算符組成的。 操作數:常量、變量、子表達式 X=(x+2)*(y-2); 運算符: 賦值運算符:= 。其作用是做賦值運算,將等號後邊的值賦值給等號前邊的。 復合賦值運算符: +=
9、C語言之預處理命令
() body 重復 分號 stdio.h ifdef 可用 style 處理 預處理命令 基本概念:ANSI C標準規定可以在C源程序中加入一些“預處理命令”,以改進程序設計環境,提高編程效率。 這些預處理命令是由ANSI C同一規定的,但是它們不是C語言本身的組
C語言#error預處理
#error 預處理指令的作用是,編譯程式時,只要遇到#error 就會生成一個編譯錯誤提示訊息,並停止編譯。其語法格式為: #error error-message 注意,巨集串error-message 不用雙引號包圍。遇到#error 指令時,錯誤資訊被顯示,可能同時還顯示編譯程式作者
C語言(預處理)
- 預處理 在程式編譯之前進行的處理,所有的編譯預處理命令以#開頭。分為巨集定義、檔案包含、條件編譯。 1. 巨集 巨集定義的作用是某段程式碼的別名,以#define開頭,結尾不用分號。 eg:#define PI 3.14//巨集名一般用大寫字母 在編譯預處理時,只是
黑馬程式設計師——————c語言之預處理命令
一、什麼是預處理指令 1、C語言在對源程式進行編譯之前,會先對一些特殊的預處理指令作解釋,產生一個新的源程式(這個過程稱為編譯預處理),之後再進行通常的編譯 2、為了區分預處理指令和一般的C語句,所有預處理指令都以符號"#"開頭,並且結尾不用分號3、預處理指令可以
C語言之預處理詳解
C語言之預處理詳解 綱要: 預定義符號 #define #define定義識別符號 #define定義巨集 #define的替換規則 #與## 幾點注意#undef 帶副作用的巨集引數 巨集和函式的對比 命名約定 命令列定義 條件編譯 單分支條件編譯 多分支條件編譯 判斷是否被定義 巢狀指令
C語言中的條件編譯及編譯預處理
在C 語言中,並沒有任何內在的機制來完成如下一些功能:在編譯時包含其他原始檔、定義巨集、根據條件決定編譯時是否包含某些程式碼。要完成這些工作,就需要使用預處理程式。儘管在目前絕大多數編譯器都包含了預處理程式,但通常認為它們是獨立於編譯器的。預處理過程讀入原始碼,檢查包含預處
C語言的編譯預處理
wall shadow c程序 方式 共享庫 blog gcc編譯 程序員 proc 1、C程序的過程 處理流程: 靜態鏈接與動態鏈接: 鏈接可以分為靜態鏈接(靜態庫)與動態鏈接(共享庫):?靜態庫是代碼的歸檔,在使用靜態庫時是采用的復制代碼的方式。共享庫是可執行文件的組
C語言詳解(6)巨集定義和條件編譯
巨集定義和條件編譯 一、概述 巨集定義是C語言的預處理功能。巨集定義就是簡單的替換,不作為計算,不也作為表示式。在C語言中作為預處理指令包括:巨集定義、檔案包含、條件編譯。 條件編譯其實就是將if…else…的設計思想引入到預處理功能中,給編譯器使用的。條件編譯時通過
深入理解C語言的預編譯指令之include
get http npe target info pdb tfs mar 語言 慫b促64u父猩84卵ml0http://www.facebolw.com/space/2101977 0俜垂屹17該性膠1http://tushu.docin.com/hmd622 6PD
C++的編譯預處理
end 裏的 const return key 大寫字母 font include 效果 C++中,在編譯器對源程序進行編譯之前,首先要由預處理對程序文本進行預處理。預處理器提供了一組預編譯處理指令和預處理操作符。預處理指令實際上不是C++語言的一部分,它只是用來擴充C
編譯預處理指令:檔案包含指令、巨集定義指令、條件編譯指令
編譯預處理指令:檔案包含指令、巨集定義指令、條件編譯指令。“#”開頭,不加分號“;” 1、檔案包含指令: #include<檔名> 標準目錄下搜尋 #include"檔名" 當前目錄下搜尋,再在標準目錄下搜尋 2、巨集定義指令: #define 巨集名 巨集文字 //巨集名習慣大寫 #
C++學習筆記——第六天 編譯預處理
預處理是指編譯器在進行第一遍掃描之前所做的工作,其由預處理程式負責完成。 目標 瞭解預處理命令的功能 掌握巨集定義及其使用 掌握檔案包含的使用 掌握常用的編譯預處理命令 預處理命令 預處理就是對原始檔進行編譯前,先對預處理部分進行處理,然後對處