C語言程序環(huán)境和預(yù)處理詳解分析
一、程序的翻譯環(huán)境和運行環(huán)境
重點:任何ANSI C(標準C的程序)的一種實現(xiàn),存在兩個不同的環(huán)境
第1種是翻譯環(huán)境,在這個環(huán)境中源代碼被轉(zhuǎn)換為可執(zhí)行的機器指令。
第2種是執(zhí)行環(huán)境,它用于實際執(zhí)行代碼。
程序的翻譯環(huán)境
翻譯環(huán)境就是我們要做的編譯和鏈接的這個動作。
1.每個.c文件都各自獨立經(jīng)過編譯器的處理,最后又各自生成一個叫目標文件的東西(windows底下目標文件就叫做obj)
2.每個目標文件由鏈接器(linker)捆綁在一起,形成一個單一而完整的可執(zhí)行程序。
3.鏈接器同時也會引入標準C函數(shù)庫中任何被該程序所用到的函數(shù),而且它可以搜索程序員個人 的程序庫,將其需要的函數(shù)也鏈接到程序中。
編譯又分為三個步驟:
預(yù)編譯(預(yù)處理):gcc test.c -E(指令)預(yù)處理后就會停止,gcc test .c -E >test.i輸出重定向到test.i這個文件 。這個階段,
1:完成了頭文件的包含,比如#include這樣指令的預(yù)處理,
2:#define定義的符號和宏的替換,
3:注釋刪除。這些都屬于文本操作。
編譯:gcc test .i -S生成 test.s文件,這階段把C語言代碼轉(zhuǎn)換成匯編代碼,1:語法分析,2:詞法分析,3:語義分析,4:符號匯總。
匯編:對.s文件進行匯編,gcc test .s - c(指令)生成test.o文件(.o文件在windows叫test.obj),這階段把匯編代碼轉(zhuǎn)換成了機器指令(二進制指令),生成符號表
鏈接階段
把多個目標文件和鏈接庫進行鏈接。
這個階段,1:合并段表,2:符號表的合并和重定位。
執(zhí)行環(huán)境(運行環(huán)境)
程序的執(zhí)行過程:
1. 程序必須載入內(nèi)存中。在有操作系統(tǒng)的環(huán)境中:一般這個由操作系統(tǒng)完成。在獨立的環(huán)境中,程序 的載入必須由手工安排,也可能是通過可執(zhí)行代碼置入只讀內(nèi)存來完成。
2. 程序的執(zhí)行便開始。接著便調(diào)用main函數(shù)。
3.開始執(zhí)行程序代碼。這個時候程序?qū)⑹褂靡粋€運行時堆棧(stack),存儲函數(shù)的局部變量和返回 地址。程序同時也可以使用靜態(tài)(static)內(nèi)存,存儲于靜態(tài)內(nèi)存中的變量在程序的整個執(zhí)行過程 一直保留他們的值。
4. 終止程序。正常終止main函數(shù);也有可能是意外終止。
二、預(yù)處理詳解
預(yù)定義符號
__FILE__ //進行編譯的源文件
__LINE__ //文件當前的行號
__DATE__ //文件被編譯的日期
__TIME__ //文件被編譯的時間
__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義
舉例:寫一個日志文件
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int i = 0; FILE* pf = fopen("log.txt", "a+"); if (NULL == pf) { perror("fopen\n"); return 1; } for (i = 0; i < 10; i++) { fprintf(pf, "%s %d %s %s %d\n", __FILE__, __LINE__, __DATE__, __TIME__, i); } return 0; }
文件內(nèi)容:
#define定義標識符
舉例:
#define定義宏
#define 機制包括了一個規(guī)定,允許把參數(shù)替換到文本中,這種實現(xiàn)通常稱為宏(macro)或定義 宏(define macro)。
這個問題,的解決辦法是在宏定義表達式兩邊加上一對括號就可以了。
#define DOUBLE( x) ( ( x ) + ( x ) )
所以用于對數(shù)值表達式進行求值的宏定義都應(yīng)該用這種方式加上括號,避免在使用宏時由于參數(shù)中 的操作符或鄰近操作符之間不可預(yù)料的相互作用。
#define 替換規(guī)則
在程序中擴展#define定義符號和宏時,需要涉及幾個步驟。
1. 在調(diào)用宏時,首先對參數(shù)進行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先 被替換。
2. 替換文本隨后被插入到程序中原來文本的位置。對于宏,參數(shù)名被他們的值所替換。
3. 最后,再次對結(jié)果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復(fù)上 述處理過程。
注意:
1. 宏參數(shù)和#define 定義中可以出現(xiàn)其他#define定義的符號。但是對于宏,不能出現(xiàn)遞歸。 2. 當預(yù)處理器搜索#define定義的符號的時候,字符串常量的內(nèi)容并不被搜索。
#和##兩個預(yù)處理的工具
#可以把參數(shù)插入到字符串中
##可以把位于它兩邊的符號合成一個符號。 它允許宏定義從分離的文本片段創(chuàng)建標識符。
帶副作用的宏參數(shù)
當宏參數(shù)在宏的定義中出現(xiàn)超過一次的時候,如果參數(shù)帶有副作用,那么你在使用這個宏的時候就可能 出現(xiàn)危險,導(dǎo)致不可預(yù)測的后果。副作用就是表達式求值的時候出現(xiàn)的永久性效果。
例如:
x+1;//不帶副作用
x++;//帶有副作用
宏和函數(shù)對比
宏通常被應(yīng)用于執(zhí)行簡單的運算。 比如在兩個數(shù)中找出較大的一個。
#define MAX(a, b) ((a)>(b)?(a):(b))
那為什么不用函數(shù)來完成這個任務(wù)?
原因有二:
1. 用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實際執(zhí)行這個小型計算工作所需要的時間更多。 所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌。
2. 更為重要的是函數(shù)的參數(shù)必須聲明為特定的類型。 所以函數(shù)只能在類型合適的表達式上使用。反之這個宏怎可以適用于整形、長整型、浮點型等可以 用于>來比較的類型。
宏是類型無關(guān)的。
宏的缺點:
當然和函數(shù)相比宏也有劣勢的地方:
1. 每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序 的長度。
2. 宏是沒法調(diào)試的
3. 宏由于類型無關(guān),也就不夠嚴謹。
4. 宏可能會帶來運算符優(yōu)先級的問題,導(dǎo)致程容易出現(xiàn)錯。
#undef移除宏
這條指令用于移除一個宏定義。
#undef NAME //如果現(xiàn)存的一個名字需要被重新定義,那么它的舊名字首先要被移除。
代碼如下:
命令行定義
許多C 的編譯器提供了一種能力,允許在命令行中定義符號。用于啟動編譯過程。 例如:當我們根據(jù)同一個源文件要編譯出不同的一個程序的不同版本的時候,這個特性有點用處。(假 定某個程序中聲明了一個某個長度的數(shù)組,如果機器內(nèi)存有限,我們需要一個很小的數(shù)組,但是另外一 個機器內(nèi)存大寫,我們需要一個數(shù)組能夠大寫。)
代碼如下:
在linux底下,通過gcc test.c -D M=100,下面的代碼就可以正常運行了
條件編譯
滿足條件就編譯,不滿足條件就不參與編譯。
代碼如下:
并不打印結(jié)果。
#if 1為真,后面就編譯,如果為0就不編譯。
頭文件包含
‘頭文件包含的兩種方式:
#include"add.h" 本地文件包含,自定義的函數(shù)的頭文件用""
#include<add.h> 庫文件包含,C語言庫中提供的函數(shù)的頭文件用<>
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標 準位置查找頭文件。 如果找不到就提示編譯錯誤
查找頭文件直接去標準路徑下去查找,如果找不到就提示編譯錯誤。 這樣是不是可以說,對于庫文件也可以使用 “” 的形式包含? 答案是肯定的,可以。 但是這樣做查找的效率就低些,當然這樣也不容易區(qū)分是庫文件還是本地文件了。
嵌套文件包含
每個頭文件的開頭寫:
#ifndef __TEST_H__
#define __TEST_H__
//頭文件的內(nèi)容
#endif //__TEST_H__
或者:
#pragma once
就可以避免頭文件的重復(fù)引入。
總結(jié)
題主太累了,總結(jié)就不寫了,已經(jīng)過了零點了,要睡覺了。實在是感覺頂不住了,明天還得早起呢,希望大家也能不忘初心,堅持學(xué)下去,大家一起努力?。。。。?!
到此這篇關(guān)于C語言程序環(huán)境和預(yù)處理詳解分析的文章就介紹到這了,更多相關(guān)C語言 預(yù)處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談C++ Explicit Constructors(顯式構(gòu)造函數(shù))
下面小編就為大家?guī)硪黄獪\談C++ Explicit Constructors(顯式構(gòu)造函數(shù))。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12C++ OpenCV學(xué)習之圖像金字塔與圖像融合詳解
圖像金字塔分為兩種:高斯金字塔和拉普拉斯金字塔。圖像金字塔在保持細節(jié)的條件下進行圖像融合等多尺度編輯操作非常有用。本文將利用圖像金字塔實現(xiàn)圖像融合,需要的可以參考一下2022-03-03C++ 動態(tài)數(shù)組模版類Vector實例詳解
這篇文章主要為大家詳細介紹了C++動態(tài)數(shù)組模版類Vector實例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02

C++數(shù)據(jù)結(jié)構(gòu)之實現(xiàn)鄰接表與鄰接矩陣的相互轉(zhuǎn)換