C語言由源代碼生成可執行程序的過程如下:
C源程序->編譯預處理->編譯->優化程序->匯編程序->鏈接程序->可執行文件
其中編譯預處理階段,讀取C源程序,對其中的預處理指令(以#開頭的指令)和特殊符號進行處理。或者說是掃描源代碼,對其進行初步的轉換,產生新的源代碼提供給編譯器。
預處理過程先於編譯器對源代碼進行處理,讀入源代碼,檢查包含預處理指令的語句和宏定義,並對源代碼進行轉換。預處理過程還會刪除程序中的注釋和多余的空白字符。
一、預處理指令
在C語言的程序中包括各種以符號#開頭的編譯指令,這些指令稱為預處理命令。預處理命令屬於C語言編譯器,而不是C語言的組成部分,通過預處理命令可擴展C語言程序設計的環境。
預處理指令是以#號開頭的代碼行,#號必須是該行除了任何空白字符外的第一個字符。
#后是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字符,整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對源代碼做某些轉換。
預處理指令主要有以下三種:
1)包含文件:將源文件中以#include格式包含的文件復制到編譯的源文件中,可以是頭文件,也可以是其它的程序文件。
2)宏定義指令:#define指令定義一個宏,#undef指令刪除一個宏定義。
3)條件編譯:根據#ifdef和#ifndef后面的條件決定需要編譯的代碼。
二、包含文件
當一個C語言程序由多個文件模塊組成時,主模塊中一般包含main函數和一些當前程序專用的函數。程序從main函數開始執行,在執行過程中,可調用當前文件中的函數,也可調用其他文件模塊中的函數。
如果在模塊中要調用其他文件模塊中的函數,首先必須在主模塊中聲明該函數原型。一般都是采用文件包含的方法,包含其他文件模塊的頭文件。
文件包含中指定的文件名即可以用引號括起來,也可以用尖括號括起來,格式如下:
#include <文件名>
或
#include "文件名"
如果使用尖括號<>括起文件名,則編譯程序將到C語言開發環境中設置好的include文件中去找指定的文件(/usr/include)。
因為C語言的標准頭文件都存放在/usr/include文件夾中,所以一般對標准頭文件采用尖括號;對程序員自己編寫的文件,則使用雙引號。
如果自己編寫的文件不是存放在當前工作文件夾,可以在#include命令后面加在路徑。
#include命令的作用是把指定的文件模塊內容插入到#include所在的位置,當程序編譯鏈接時,系統會把所有#include指定的文件鏈接生成可執行代碼。
#include包含文件,可以是“.h”,表示C語言程序的頭文件,也可以是“.c”,表示包含普通C語言源程序。
三、宏定義指令
使用#define命令並不是真正的定義符號常量,而是定義一個可以替換的宏。被定義為宏的標識符稱為“宏名”。在編譯預處理過程時,對程序中所有出現的“宏名”,都用宏定義中的字符串去代換,這稱為“宏替換”或“宏展開”。
在C語言中,宏分為有參數和無參數兩種。
1、無參數的宏
其定義格式如下:
#define 宏名 字符串
在以上宏定義語句中,各部分的含義如下:
# 表示這是一條預處理命令(凡是以“#”開始的均為預處理命令)。
define 關鍵字“define”為宏定義命令。
宏名 是一個標示符,必須符合C語言標示符的規定,一般以大寫字母標識宏名。
字符串可以是常數,表達式,格式串等。在前面使用的符號常量的定義就是一個無參數宏定義。
注意:預處理命令語句后面一般不會添加分號,如果在#define最后有分號,在宏替換時分號也將替換到源代碼中去。在宏名和字符串之間可以有任意個空格。
#define PI 3.141592
示例(book149.c)
/*
* 程序名:book149.c,此程序演示不帶參數的宏。
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#define PI 3.141592
int main()
{
printf("PI is %lf\n",PI);
}
執行預編譯指令gcc -E -o book149.E book149.c,得到book149.E文件,如下:
# 1 "book149.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<命令行>" 2
# 1 "book149.c"
int main()
{
printf("PI is %lf\n",3.141592);
}
在使用宏定義時,還需要注意以下幾點:
宏定義是宏名來表示一個字符串,在宏展開時又以該字符串取代宏名。這只是一種簡單的代換,字符串中可以含任何字符,可以是常數,也可以是表達式,預處理程序對它不作任何語法檢查。如有錯誤,只能在編譯已被宏展開后的源程序時發現。
宏定義允許嵌套,在宏定義的字符串中可以使用已經定義的宏名。在宏展開時由預處理程序層層替換。建議不要這么做,會把程序復雜化。
習慣上宏名用大寫字母表示,以方便與變量區別。但也可以用小寫字母。
2、帶參數的宏
#define命令定義宏時,還可以為宏設置參數。與函數中的參數類似,在宏定義中的參數為形式參數,在宏調用中的參數稱為實際參數。對帶參數的宏,在調用中,不僅要宏展開,還要用實參去代換形參。
帶參宏定義的一般形式為:
#define 宏名(形參表) 字符串
在定義帶參數的宏時,宏名和形參表之間不能有空格出現,否則,就將宏定義成為無參數形式,而導致程序出錯。
#define MAX(x,y) ((x)>(y) ? (x) : (y))
以上的宏定義中,如果x的值大於y,得到x,否則得到y。
示例(book150.c)
/*
* 程序名:book150.c,此程序演示帶參數的宏
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
printf("MAX is %d\n",MAX(34,59));
}
執行預編譯指令gcc -E -o book150.E book150.c,得到book50.E文件,如下:
# 1 "book150.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<命令行>" 2
# 1 "book150.c"
int main()
{
printf("MAX is %d\n",((34)>(59)?(34):(59)));
}
帶參的宏和帶參的函數相似,但其本質是不同的。使用帶參宏時,在預處理時將程序源代碼替換到相應的位置,編譯時得到完整的目標代碼,而不進行函數調用,因此程序執行效率要高些。而函數調用只需要編譯一次函數,代碼量較少,一般情況下,對於簡單的功能,可使用宏替換的形式來使用。
帶參數的宏不容易理解,所以,在實際開發中,我不建議使用帶參數的宏。
四、條件編譯
條件編譯有多種格式,在這里我只介紹最常用的兩種格式#ifdef和#ifndef。
1、#ifdef
#ifdef命令的使用格式如下:
#ifdef 標識符
程序段 1
#else
程序段 2
#endif
其意義是,如果#ifdef后面的標識符已被定義過,則對“程序段1”進行編譯;如果沒有定義標識符,則編譯“程序段2”。一般不使用#else及后面的“程序2”。
示例(book153.c)
/*
* 程序名:book153.c,此程序用於演示#ifdef條件編譯
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#define LINUX
int main()
{
#ifdef LINUX
printf("這是Linux操作系統。\n");
#else
printf("未知的操作系統。\n");
#endif
}
執行預編譯指令gcc -E -o book153.E book153.c,得到book153.E文件,如下:
# 1 "book153.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<命令行>" 2
# 1 "book153.c"
int main()
{
printf("這是Linux操作系統。\n");
}
2、#ifndef
而#ifndef的意義與#ifdef相反,其格式如下:
#ifndef 標識符
程序段 1
#else
程序段 2
#endif
其意義是,如果未定義標識符,則編譯“程序段1”;否則編譯“程序段2”
在實際開發中,程序員用#ifndef來防止頭文件被重復包含。
我們打開/usr/include/stdio.h文件。
第一條有效行的代碼是。
#ifndef _STDIO_H
接下來是。
# define _STDIO_H 1
最后一行是。
#endif
程序員自定義的頭文件,我們也會這么寫,如:
/*
* 程序名:_public.h,公共功能函數聲明的頭文件
* 作者:C語言技術網(www.freecplus.net) 日期:20190525
*/
#ifndef _PUBLIC_H
#define _PUBLIC_H 1
// 把字符串格式的時間轉換為整數的時間,函數的聲明如下:
int strtotime(const char *strtime,time_t *ti);
#endif
如果頭文件被包含多次,就表示頭文件中的函數被多次聲明,全局變量被多次定義,在以前的C語言編譯器中,這是不允許的,編譯時會報錯,但是,現在的部分編譯器比較智能,多次定義全局變量或多次聲明函數也不會報錯。這些新的特征讓我這個老家伙很不適應。
3、#undef
undef取消已定義的標識符。
五、課后作業
1)編寫示例程序,把本章節介紹的知識點全部演示一遍,用程序演示可以加深您的理解和映象。
2)編寫代碼測試多次聲明同名的函數、多次定義同名的函數、多次聲明同名的全局變量、多次聲明同名的局部變量。
六、版權聲明
C語言技術網原創文章,轉載請說明文章的來源、作者和原文的鏈接。
來源:C語言技術網(www.freecplus.net)
作者:碼農有道
如果文章有錯別字,或者內容有錯誤,或其他的建議和意見,請您留言指正,非常感謝!!!