政府網(wǎng)站集約化建設(shè)經(jīng)驗(yàn)百度入口官網(wǎng)
目錄
1.程序的翻譯環(huán)境和執(zhí)行環(huán)境
2.詳解編譯+鏈接
? 2.1 翻譯環(huán)境
? 2.2 編譯本身也分為幾個(gè)階段:
? 2.3 運(yùn)行環(huán)境
3.預(yù)處理詳解
? 3.1預(yù)定符號(hào)
? 3.2 #define
? 3.3 #undef
? 3.4 命令行定義
? 3.5 條件編譯
? 3.6 文件包含
了解重點(diǎn):
- 程序的翻譯環(huán)境
- 程序的執(zhí)行環(huán)境
- 詳解: C語言程序的編譯+鏈接
- 預(yù)定義符號(hào)介紹
- 預(yù)處理指令#define
- 宏和函數(shù)的對(duì)比
- 預(yù)處理操作符#和##的介紹
- 命令定義預(yù)處理指令#include
- 預(yù)處理指令#undef
- 條件編譯
1.程序的翻譯環(huán)境和執(zhí)行環(huán)境
在ANSI C的任何一種實(shí)現(xiàn)中,存在兩個(gè)不同的環(huán)境
- 第1種是翻譯環(huán)境,在這個(gè)環(huán)境中,代碼被轉(zhuǎn)換為可執(zhí)行的機(jī)器指令。
- 第2種是執(zhí)行環(huán)境,它用于實(shí)際執(zhí)行代碼。
2.詳解編譯+鏈接
? 2.1 翻譯環(huán)境
?
- 組成一個(gè)程序的每一個(gè)源文件通過編譯過程分別轉(zhuǎn)換成目標(biāo)文件。
- 每個(gè)目標(biāo)文件有鏈接器捆綁在一起,形成一個(gè)單一而完整的可執(zhí)行程序。
- 鏈接器同時(shí)也會(huì)引入標(biāo)準(zhǔn)C函數(shù)庫中任何被該程序所用到的函數(shù),而且它可以搜索程序員個(gè)人的程序庫,將其需要的函數(shù)也鏈接到程序中。
? 2.2 編譯本身也分為幾個(gè)階段:
編譯 | 鏈接 | ||
預(yù)編譯階段 | 編譯 | 匯編 | 鏈接 |
1.頭文件#include 2.define定義符號(hào)、宏替換 | 語法分析 語義分析 符號(hào)匯總 | 將匯編代碼轉(zhuǎn) 換成二進(jìn)制指令 | 1.合并段表 2.符號(hào)表的合并和 符號(hào)表的重定位 |
? 2.3 運(yùn)行環(huán)境
程序執(zhí)行的過程:
- 程序必須載入內(nèi)存中。在有操作系統(tǒng)的環(huán)境中:一般這個(gè)由操作系統(tǒng)完成。在獨(dú)立的環(huán)境中,程序的載入必須由手工安排,也可能是通過可執(zhí)行代碼置入只讀內(nèi)存來完成
- 程序的執(zhí)行便開始。接著便調(diào)用main函數(shù)。
- 開始執(zhí)行程序代碼。這個(gè)時(shí)候?qū)W將甫注環(huán)運(yùn)待的中k),存儲(chǔ)函數(shù)的局部變量和返回地址。程序同時(shí)也可以使用靜態(tài) (static)內(nèi)存,存儲(chǔ)于靜態(tài)內(nèi)存中的變量在程序的整個(gè)執(zhí)行過程一直保留他們的值。
- 終止程序。正常終止main函數(shù);也有可能是意外終止。
3.預(yù)處理詳解
? 3.1預(yù)定符號(hào)
- __FILE__? ? ? ?//進(jìn)行編譯的源文件
- __LINE__? ? ? //文件當(dāng)前的行號(hào)
- __DATE__? ? ?//文件被編譯的日期
- __TIME__? ? ?//文件被編譯的時(shí)間
- __STDC__? ? //如果編譯器遵循ANSI C,其值為1,否則未定義
這些預(yù)定義符號(hào)都是語言內(nèi)置的
舉個(gè)例子:?
#include<stdio.h>
int main()
{printf("%s\n",__FILE__);printf("%d\n",__LINE__);printf("%s\n",__DATE__);printf("%s\n",__TIME__);return 0;
}
? 3.2 #define
????????3.2.1 #define 定義標(biāo)識(shí)符
#define M 100
int main()
{int m = M;printf("%d\n",m);return 0;
}
????????3.2.2 #define 定義宏
- #define 機(jī)制包括了一個(gè)規(guī)定,允許把參數(shù)替換到文本中,這種實(shí)現(xiàn)通常稱為宏 (macro) 或定義宏(define macro)。
- 下面是宏的申明方式
#define name( parament-list ) stuff
其中的parament-list是一個(gè)由逗號(hào)隔開的符號(hào)表,它們可能出現(xiàn)在stuff中。 - 注意:
參數(shù)列表的左括號(hào)必須與name緊鄰。
如果兩者之間有任何空白存在,參數(shù)列表就會(huì)被解釋為stuff的一部分。
#define SQUARE(x) x*x
int main()
{printf("%d\n",SQUARE(3));return 0;
}
????????再舉例:乍一看,你可能覺得這段代碼將打印16這個(gè)值
????????卻打印出來7,為什么呢?
#define SQUARE(x) x*x
int main()
{printf("%d\n",SQUARE(3+1)); //這里不會(huì)先計(jì)算3+1,而是直接替換printf("%d\n",3+1*3+1);//所以輸出7return 0;
}
????????所以寫成以下代碼:
#define SQUARE(x) ((x)*(x))//這里不會(huì)先計(jì)算3+1,因?yàn)樘砑恿死ㄌ?hào)
int main()
{printf("%d\n",SQUARE(3+1)); printf("%d\n",(3+1)*(3+1));//所以輸出16return 0;
}
????????3.2.3 #define 替換規(guī)則
????????在程序中擴(kuò)展#define定義符號(hào)和宏時(shí),需要涉及幾個(gè)步驟。
- 在調(diào)用宏時(shí),首先對(duì)參數(shù)進(jìn)行檢查,看看是否包含任何由#define定義的符號(hào)。如果是,它們首先被替換。
- 替換文本隨后被插入到程序中原來文本的位置。對(duì)于宏,參數(shù)名被他們的值替換。
- 最后,再次對(duì)結(jié)果文件進(jìn)行掃描,看看它是否包含任何由#define定義的符號(hào)。如果是,就重復(fù)上述處理過程。
????????注意:
- 宏參數(shù)和#define 定義中可以出現(xiàn)其他#define定義的變量。但是對(duì)于宏,不能出現(xiàn)遞歸。
- 當(dāng)預(yù)處理器搜索#define定義的符號(hào)的時(shí)候,字符串常量的內(nèi)容并不被搜索。?
????????3.2.4 #和##
????????如何把參數(shù)插入到字符串中?
????????首先我們看看這樣的代碼:
? ? ? ? 寫一個(gè)宏:要求:
? ? ? ? 當(dāng)輸入a時(shí)候輸出:the value of a is 10
????????當(dāng)輸入b時(shí)候輸出the value of b is 20
? ? ? ? 當(dāng)輸入c時(shí)候輸出?the value of c is 30
#define PRINT(x) printf("the value of "#x" is %d\n",x);
int main()
{int a = 10;PRINT(a);int b = 20;PRINT(b);int c = 30;PRINT(c);return 0;
}
????????再完整一點(diǎn)?
#define PRINT(x,format) printf("the value of "#x" is "format"\n",x);
int main()
{int a = 10;PRINT(a,"%d");int b = 20;PRINT(b,"%d");float c = 5.5f;PRINT(c,"%f");return 0;
}
????????##用來拼接字符
#define CAT(x,y) x##y
int main()
{int class101 = 300;printf("%d\n",CAT(class,101));//這里相當(dāng)于printf("%d\n",class101);return 0;
}
????????3.2.5 帶副作用的宏參數(shù)
- 當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過一次的時(shí)候,如果參數(shù)帶者作用那么你在使用這個(gè)宏的時(shí)候就可能出現(xiàn)危險(xiǎn),導(dǎo)致不可預(yù)測(cè)的后果。副作用就是表達(dá)式求值的時(shí)候出現(xiàn)的永久性效果。
????????x+1;? //不帶副作用
????????x++;? //帶有副作用?
例如:
#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{int a = 5;int b = 8;int m = MAX(a++,b++);printf("%d %d\n",a,b); //輸出6 10//在宏中發(fā)生了變化return 0;
}
????????3.2.6 宏和函數(shù)對(duì)比
????????宏通常被應(yīng)用于執(zhí)行簡(jiǎn)單的運(yùn)算。比如在兩個(gè)數(shù)中越出較大的一個(gè)。
#define MAX(x,y) ((x)>(y)?(x):(y))
那為什么不用函數(shù)來完成這個(gè)任務(wù)?
【原因有二:】
1.用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實(shí)際執(zhí)行這個(gè)小型計(jì)算工作所需要的時(shí)間更多。所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌。
2.更為重要的是函數(shù)的參數(shù)必須聲明為特定的類型。所以函數(shù)只能在類型合適的表達(dá)式上使用。反之這個(gè)宏怎可以適用于整形、長(zhǎng)整型、浮點(diǎn)型等可以用于>來比較的類型。宏是類型無關(guān)的。?
【當(dāng)然和宏相比函數(shù)也有劣勢(shì)的地方:】
1.每次使用宏的時(shí)候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長(zhǎng)度。
2.宏是沒法調(diào)試的。
3.宏由于類型無關(guān),也就不夠嚴(yán)謹(jǐn)。
4.宏可能會(huì)帶來運(yùn)算符優(yōu)先級(jí)的問題,導(dǎo)致程容易出現(xiàn)錯(cuò)?。
- 宏有時(shí)候可以做函數(shù)做不到的事情。比如:宏的參數(shù)可以出現(xiàn)類型,但是函數(shù)做不到.
?【宏和函數(shù)額對(duì)比】
屬性 | #define定義宏 | 函數(shù) |
代碼長(zhǎng)度 | 每次使用時(shí),宏代碼都會(huì)被插入到程序中。除了非常小的宏之外,程序的長(zhǎng)度會(huì)大幅度增長(zhǎng) | 函數(shù)代碼只出現(xiàn)于一個(gè)地方;每次使用這個(gè)函數(shù)時(shí),都調(diào)用那個(gè)地方的同一份代碼 |
執(zhí)行速度 | 更快 | 存在函數(shù)的調(diào)用和返回的額外開銷,所以相對(duì)慢一些 |
操作符優(yōu)先級(jí) | 宏參數(shù)的求值是在所有周圍表達(dá)式的上下文環(huán)境里,除非加上括號(hào),否則鄰近操作符的優(yōu)先級(jí)可能會(huì)產(chǎn)生不可預(yù)料的后果,所以建議宏在書寫的時(shí)候多些括號(hào)。 | 函數(shù)參數(shù)只在函數(shù)調(diào)用的時(shí)候求值一次,它的結(jié)果值傳遞給函數(shù)。表達(dá)式的求值結(jié)果更容易預(yù)測(cè)。 |
帶有副作用的參數(shù) | 參數(shù)可能被替換到宏體中的多個(gè)位置,所以帶有副作用的參數(shù)求值可能會(huì)產(chǎn)生不可預(yù)料的結(jié)果 | 函數(shù)參數(shù)只在傳參的時(shí)候求值一次,結(jié)果更容易控制。 |
參數(shù)類型 | 宏的參數(shù)與類型無關(guān),只要對(duì)參數(shù)的操作是合法的,它就可以使用于任何參數(shù)類型。 | 函數(shù)的參數(shù)是與類型有關(guān)的,如果參數(shù)的類型不同,就需要不同的函數(shù),即使他們執(zhí)行的任務(wù)是不同的 |
調(diào)試 | 宏是不方便調(diào)試的 | 函數(shù)是可以逐語句調(diào)試的 |
遞歸 | 宏是不能遞歸的 | 函數(shù)是可以遞歸的 |
【命名約定】
一般來講函數(shù)的宏的使用語法很相似。所以語言本身沒法幫我們區(qū)分二者那我們平時(shí)的一個(gè)習(xí)慣是:
- 把宏名全部大寫
- 函數(shù)名不要全部大寫?
? 3.3 #undef
這條指令用于移除一個(gè)宏定義
#undef NAME? ? //如果現(xiàn)存的一個(gè)名字需要被重新定義,那么它的舊名字首先要被移除
? 3.4 命令行定義
- 許多C的編譯器提供了一種能力,允許在命令行中定義符號(hào)。用于啟動(dòng)編譯過程。
- 例如:當(dāng)我們根據(jù)同一個(gè)源文件要編譯出不同的一個(gè)程序的不同版本的時(shí)候,這個(gè)特性有點(diǎn)用處。(假定某個(gè)程序中聲明了一個(gè)某個(gè)長(zhǎng)度的數(shù)組,如果機(jī)器內(nèi)存有限,我們需要一個(gè)很小的數(shù)組,但是另外一個(gè)機(jī)器內(nèi)存大些,我們需要一個(gè)數(shù)組能夠大些。)
? 3.5 條件編譯
- 在編譯一個(gè)程序的時(shí)候我們?nèi)绻獙⒁粭l語句(一組語句)編譯或者放棄是很方便的。因?yàn)槲覀冇袟l件編譯指令。
- 比如說:
調(diào)試性的代碼,刪除可惜,保留又礙事,所以我們可以選擇性的編譯
如下:沒有定義PRINT,就不會(huì)打印hehe
int main()
{
#ifdef PRINTprintf("hehe\n");
#endifreturn 0;
}
?如下:定義過了PRINT,就會(huì)打印hehe
#define PRINT
int main()
{
#ifdef PRINTprintf("hehe\n");
#endifreturn 0;
}
? 3.6 文件包含
????????3.6.1 文件被包含式:
- 我們已經(jīng)知道,#include 指令可以使另外一個(gè)文件被編譯。就像它實(shí)際出現(xiàn)于 #include 指令的地方一樣。
- 這種替換的方式很簡(jiǎn)單:
預(yù)處理器先刪除這條指令,并用包含文件的內(nèi)容替換。
這樣一個(gè)源文件被包含10次,那就實(shí)際被編譯10次 - 本地文件包含:
#include"filename"
查找策略: 先在源文件所在目錄下查找,
如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件。
如果找不到就提示編譯錯(cuò)誤。 - 庫文件包含
#include <filename.h>
查找頭文件直接去標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編譯錯(cuò)誤
這樣是不是可以說,對(duì)于庫文件也可以使用“”的形式包含?答案是肯定的,可以。 - Linux環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:
/usr/incIude - VS環(huán)境的標(biāo)準(zhǔn)頭文件的路徑:
C:\Program Files (x86)\Microsoft Visual?studio 12.0 \VC\include
????????3.6.2 套文件包含?
- 如何解決這個(gè)問題?
答案:條件編譯。
每個(gè)頭文件的開頭寫
#ifndef? __TEST_H__
#define? __TEST_H__
//頭文件的內(nèi)容
#endif? ?//__TEST_H__ - 或者:
#pragma once
就可以避免頭文件的重復(fù)引入。
C語言初識(shí)(1) | C簡(jiǎn)介 | 主函數(shù) | 數(shù)據(jù)類型
C語言初識(shí)(2) | 變量和常量?
C語言初識(shí)(3) | 字符串 | 轉(zhuǎn)譯字符?
C語言初識(shí)(4) | 順序 | 選擇 | 循環(huán)?
C語言初識(shí)(5) | 函數(shù) | 數(shù)組 | 操作符?
C語言初識(shí)(6) | 關(guān)鍵字?
C語言初識(shí)(7) | 指針| 結(jié)構(gòu)體 | define?
C語言初階(8) | 選擇結(jié)構(gòu) | if_else | switch?
C語言初階(9) | while | break | continue | getchar?
C語言初階(10) | 初識(shí)for循環(huán) | 入門?