欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C語言程序環(huán)境中的預處理詳解

 更新時間:2022年02月27日 17:17:01   作者:蔡欣致  
這篇文章主要為大家詳細介紹了C語言程序環(huán)境中的預處理,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助

一、翻譯環(huán)境

整個翻譯環(huán)境大致就可以畫成這樣一張圖。

下列有幾點需要說明:

1. 組成一個程序的每一個源文件通過編譯過程分別轉(zhuǎn)換成目標文件(在Linux中目標文件的后綴為.o;而在Windows中目標文件后綴為.obj)

2. 每個目標文件由鏈接器(linker)捆綁在一起,形成一個單一而完整的可執(zhí)行程序

3. 鏈接器同時也會引入標準C函數(shù)庫(鏈接庫)中任何被該程序所用到的函數(shù),而且它可以搜索程序員個人的程序庫,將其需要的函數(shù)也鏈接到程序中

接下來介紹每一步在Linux系統(tǒng)下整個翻譯環(huán)境的實現(xiàn)方法,以及每一個步驟的作用。

編譯可分為三個部分:

(1)預處理:輸入指令gcc -E test.c -o,就會將test.c文件變?yōu)閠est.i文件。這一步的作用是是對頭文件(#include)的包含、刪除注釋、#define定義符號的替換等文本操作(下文會對預處理這一個步驟展開詳細的介紹)

(2)編譯:輸入指令gcc -S test.i,就會將test.i文件變?yōu)閠est.s文件,這一步主要作用是把C語言代碼轉(zhuǎn)換成匯編代碼,其中包含4步:1. 語法分析;2. 詞法分析;3. 語義分析;4. 符號匯總

(3)匯編:輸入指令gcc -c test.s,就會將test.s文件變?yōu)閠est.o文件,這一步是把匯編代碼轉(zhuǎn)換成二進制的指令,這一步是會形成符號表,此時的符號表為接下來的鏈接操作做出了準備

多個.c文件通過編譯過程后形成.o目標文件,在要執(zhí)行鏈接的時候,輸入指令gcc test.o add.o -o test,就會將.o文件變成可執(zhí)行文件,這其中的操作包括合并段表和符號表的合并和重定位,這一步主要就是將多個目標文件進行連接的時候通過符號表查看來自外部的符號是否真實存在,這樣就完成了整個翻譯環(huán)境的操作。

二、執(zhí)行環(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ù),也有可能是意外終止的情況

三、預處理

1. 預處理符號

在C語言中,有些預處理符號是語言內(nèi)置的,就比如:

__FILE__   //進行編譯的源文件
__LINE__   //文件當前的行號
__DATE__   //文件被編譯的日期
__TIME__   //文件被編譯的時間
__STDC__   //如果編譯器遵循ANSI C,其值為1,否則未定義

2. #define定義標識符

#define定義的標識符可以是常量、簡化關鍵字、一些符號等,例如:

#define M 10   //定義常量
#define reg register   //將關鍵字簡化
#define do_forever for(;;)   //用形象的符號來替換一種實現(xiàn)
#define CASE break;case   //在寫case語句的時候會自動地把break寫上

對于#define定義標識符來說,如果定義的東西過長,還可以分幾行來寫,除最后一行外,其他每行都加上'\',例如:

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
							date:%s\ttime:%s\n",\
							__FILE__,__LINE__, \
							__DATE__,__TIME__)

3. #define定義宏

在#define定義標識符外,#define還有一個規(guī)定,就是允許把參數(shù)替換到文本中,進而就形成了#define定義宏。聲明的方式如下:

#define name(parament-list) stuff

這里的parament-list是由一個逗號隔開的符號表,在實際的代碼中他們也會存在于stuff中。

其中值得注意的是:

1. 參數(shù)列表的左括號必須與name相鄰

2. 如果parament-list與stuff兩者之間有任何空白存在,參數(shù)列表就會被注釋為stuff的一部分

了解了#define定義宏是如何寫后,接下來就是#define定義宏的替換規(guī)則:

1. 在調(diào)用宏的時候,首先對參數(shù)進行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先被替換

2. 替換文本隨后被插入到程序中原來的文本位置,參數(shù)名被它們的值所替換

3. 最后,再次對結(jié)果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復上述處理過程

所以,總結(jié)以上規(guī)則后得出的結(jié)論就是:如果是#define定義宏用于對數(shù)值表達式進行求值的宏定義都應該加上括號,避免在使用宏時由于參數(shù)中的操作符或者鄰近操作符之間不可預料的相互作用。

當然,對于#define的使用還有幾個注意的點:

1. 宏參數(shù)和#define定義中可以出現(xiàn)其他#define定義的符號,但是對于宏,不能出現(xiàn)遞歸

2. 當預處理器搜索#define定義的符號的時候,字符串常量的內(nèi)容并不被搜索

4. #和##

對于一些想要把參數(shù)插入到字符串中的情況,我們會使用#來把一個宏參數(shù)變成對應的字符串,下面舉個例子:

如果是直接打印出來的話,因為字符串是可以拼接的,所以就如這樣:

#include <stdio.h>
int main()
{
	int a = 10;
	printf("the value of ""a"" is %d\n", a);
	return 0;
}

那么,對于定義宏參數(shù)來說,就應該這樣:

#include <stdio.h>
#define PRINT(n) printf("the value of "#n" is %d\n", n)
int main()
{
	int a = 10;
	PRINT(a);
	return 0;
}

這樣字符串中的n才會根據(jù)跟著宏參數(shù)的值變化而變化。

而##的作用是可以把位于它兩邊的符號合成一個符號。它允許宏定義從分離的文本段創(chuàng)建標識符。但是這樣連接必須產(chǎn)生一個合法的標識符,否則會報錯說未定義標識符。

5. 宏和函數(shù)的對比

宏的優(yōu)勢:1. 在執(zhí)行一些小型計算工作的時候,定義宏比調(diào)用函數(shù)和從函數(shù)返回的代碼執(zhí)行所需要的時間會更短;2. 函數(shù)的參數(shù)必須聲明為特定的類型,二宏參數(shù)不用

宏的劣勢:1. 每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長度;2. 宏是無法進行調(diào)試的,而函數(shù)可以;3. 宏由于沒有進行類型定義,所以有時候就會不夠嚴謹;4. 宏可能會帶來運算符的優(yōu)先級的問題,導致程序容易出錯

屬性#define定義宏函數(shù)
代碼長度每次使用時,宏代碼都會被插入到程序中,除了非常小的宏以外,程序的長度會大幅度增長函數(shù)代碼只出現(xiàn)于一個地方;每次使用這個函數(shù)時,都調(diào)用那個地方的同一份代碼
執(zhí)行速度更快存在函數(shù)的使用和返回的額外開銷,所以相對慢一些
操作符優(yōu)先級宏參數(shù)的求值是在所有周圍表達式上下文環(huán)境里,除非加上括號,否則鄰近操作符的優(yōu)先級可能會產(chǎn)生不可預料的后果,所以建議宏在書寫的時候多些括號函數(shù)參數(shù)只在函數(shù)調(diào)用的時候求值一次,它的結(jié)果值傳給函數(shù)。表達式的求值結(jié)果更容易預測
帶有副作用的參數(shù)參數(shù)可能被替換到宏體中的多個位置,所以帶有副作用的參數(shù)求值可能會產(chǎn)生不可預料的結(jié)果函數(shù)參數(shù)只在傳參的時候求值一次,結(jié)果更容易控制
參數(shù)類型宏的參數(shù)與類型無關,只要參數(shù)的操作是合法的,它就可以使用于任何參數(shù)類型函數(shù)的參數(shù)是與類型有關的,如果參數(shù)類型不同,就需要不同的參數(shù),即使他們執(zhí)行的任務的不同的
調(diào)試宏是不方便調(diào)試的函數(shù)是可以逐語句調(diào)試的
遞歸宏是不能遞歸的函數(shù)是可以遞歸的

6. 條件編譯

下面列舉一些編譯指令:

1. #undef 該指令用于移除一個宏定義

2. 該指令是判斷應該執(zhí)行哪一個語句塊

#if 常量表達式
    執(zhí)行語塊
#elif 常量表達式
    執(zhí)行語塊
#else
    執(zhí)行語塊
#endif

3. 該指令是判斷是否被定義

#if define(symbol)
    如果有定義,執(zhí)行此語句塊
or
#ifdef symbol
    如果有定義,執(zhí)行此語句塊
or
#if !define(symbol)
    如果沒有定義,執(zhí)行此語句塊
or
#ifndef symbol
    如果沒有定義,執(zhí)行此語句塊

4. 對于條件編譯指令來說,其實還可以對其進行嵌套,稱為嵌套指令

7. 文件包含

我們在一些較大工程進行編譯的時候、在多人合作同一塊項目工程的時候,可能會出現(xiàn)頭文件重復包含的情況,如果真是這樣,則會導致整個代碼運行時的效率大大降低,所以對頭文件避免重復包含就顯得十分重要了。那么,如何避免呢?下面就有一段代碼可以用來避免這種情況:

#ifndef __TEST_H__
#define __TEST_H__
    寫頭文件內(nèi)容
#endif

這段代碼就可以很好地解決了頭文件重復包含的問題,但是實際上,如果是在VS的環(huán)境下進行編譯,會自動在最開始的地方寫上:#pragma once,這句代碼一樣也是可以解決重復包含的問題。

那么,解決完頭文件重復包含的問題后,就來介紹兩種頭文件包含的方式:

1. 用引號包含的頭文件,例如:#include "test.h"。這種包含方式頭文件的查找策略是先在源文件所在的目錄下查找,如果該頭文件未被找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標準位置查找頭文件,如果還找不到,則會直接報錯。

2. 用尖括號包含頭文件,例如:#include 。這種包含方式則是未有第一步,直接進行第二步。

但是不能說為了保證萬無一失,直接把全部頭文件的包含都用引號進行包含,這樣的話有些時候其實是用尖括號的情況而錯用引號導致程序的執(zhí)行速度下降、效率下降等。

總結(jié)

本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內(nèi)容!     

相關文章

  • C++11 Unicode編碼轉(zhuǎn)換

    C++11 Unicode編碼轉(zhuǎn)換

    這篇文章主要介紹了C++11 Unicode編碼轉(zhuǎn)換的相關資料,幫助大家更好的理解和學習c++11,感興趣的朋友可以了解下
    2020-08-08
  • C語言中多樣式的格式控制符的使用詳解

    C語言中多樣式的格式控制符的使用詳解

    C語言中的格式控制符類型繁多,雖然格式字符可能就那么幾個,但組合起來使得格式控制符變得多樣化,這對于C語言初學者來說無疑是一種痛苦,為此我將常見的格式控制符類型及意義總結(jié)一下,希望對大家有所幫助
    2023-03-03
  • C&C++設計風格選擇 命名規(guī)范

    C&C++設計風格選擇 命名規(guī)范

    本文難免帶有主觀選擇傾向,但是會盡量保持客觀的態(tài)度歸納幾種主流的命名風格,僅供參考
    2018-04-04
  • 一文詳解Qt如何讀取和寫入配置文件的數(shù)據(jù)

    一文詳解Qt如何讀取和寫入配置文件的數(shù)據(jù)

    這篇文章主要為大家詳細介紹了在Qt中如何實現(xiàn)讀取和寫入配置文件的數(shù)據(jù),文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解一下
    2023-03-03
  • C++實現(xiàn)LeetCode(148.鏈表排序)

    C++實現(xiàn)LeetCode(148.鏈表排序)

    這篇文章主要介紹了C++實現(xiàn)LeetCode(148.鏈表排序),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • C++通過文件指針獲取文件大小的方法實現(xiàn)

    C++通過文件指針獲取文件大小的方法實現(xiàn)

    本文主要介紹了C++通過文件指針獲取文件大小的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 用c語言根據(jù)可變參數(shù)合成字符串的實現(xiàn)代碼

    用c語言根據(jù)可變參數(shù)合成字符串的實現(xiàn)代碼

    本篇文章是對用c語言根據(jù)可變參數(shù)合成字符串的方法進行了詳細的分析介紹,需要的朋友參考下
    2013-05-05
  • C語言簡明講解三目運算符和逗號表達式的使用

    C語言簡明講解三目運算符和逗號表達式的使用

    三目運算符,又稱條件運算符,它是唯一有3個操作數(shù)的運算符,有時又稱為三元運算符。三目運算符的結(jié)合性是右結(jié)合的;逗號表達式,是c語言中的逗號運算符,優(yōu)先級別最低,它將兩個及其以上的式子聯(lián)接起來,從左往右逐個計算表達式,整個表達式的值為最后一個表達式的值
    2022-04-04
  • C++實現(xiàn)將數(shù)組中的值反轉(zhuǎn)

    C++實現(xiàn)將數(shù)組中的值反轉(zhuǎn)

    這里給大家分享的事一則C++實現(xiàn)將數(shù)組中的值反轉(zhuǎn)的代碼,取材自《C++程序設計》(梁勇著第三版367頁),有需要的小伙伴可以參考下
    2016-05-05
  • QT判斷兩個日期時間的大小

    QT判斷兩個日期時間的大小

    本文主要介紹了QT判斷兩個日期時間的大小,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04

最新評論