日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C語言程序環境和預處理詳解分析_C 語言

作者:K穩重 ? 更新時間: 2022-05-18 編程語言

一、程序的翻譯環境和運行環境

重點:任何ANSI C(標準C的程序)的一種實現,存在兩個不同的環境

第1種是翻譯環境,在這個環境中源代碼被轉換為可執行的機器指令。

第2種是執行環境,它用于實際執行代碼。

程序的翻譯環境

翻譯環境就是我們要做的編譯和鏈接的這個動作。

1.每個.c文件都各自獨立經過編譯器的處理,最后又各自生成一個叫目標文件的東西(windows底下目標文件就叫做obj)

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

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

?編譯又分為三個步驟:

預編譯(預處理):gcc test.c -E(指令)預處理后就會停止,gcc test .c -E >test.i輸出重定向到test.i這個文件?。這個階段,

1:完成了頭文件的包含,比如#include這樣指令的預處理,

2:#define定義的符號和宏的替換,

3:注釋刪除。這些都屬于文本操作。

編譯:gcc test .i -S生成 test.s文件,這階段把C語言代碼轉換成匯編代碼,1:語法分析,2:詞法分析,3:語義分析,4:符號匯總。?

?匯編:對.s文件進行匯編,gcc test .s - c(指令)生成test.o文件(.o文件在windows叫test.obj),這階段把匯編代碼轉換成了機器指令(二進制指令),生成符號表

鏈接階段

把多個目標文件和鏈接庫進行鏈接。

這個階段,1:合并段表,2:符號表的合并和重定位。

執行環境(運行環境)

程序的執行過程:

1. 程序必須載入內存中。在有操作系統的環境中:一般這個由操作系統完成。在獨立的環境中,程序 的載入必須由手工安排,也可能是通過可執行代碼置入只讀內存來完成。

2. 程序的執行便開始。接著便調用main函數。

3.開始執行程序代碼。這個時候程序將使用一個運行時堆棧(stack),存儲函數的局部變量和返回 地址。程序同時也可以使用靜態(static)內存,存儲于靜態內存中的變量在程序的整個執行過程 一直保留他們的值。

4. 終止程序。正常終止main函數;也有可能是意外終止。

二、預處理詳解

預定義符號

__FILE__ ? ? ?//進行編譯的源文件

__LINE__ ? ? //文件當前的行號

__DATE__ ? ?//文件被編譯的日期

__TIME__ ? ?//文件被編譯的時間

__STDC__ ? ?//如果編譯器遵循ANSI C,其值為1,否則未定義

舉例:寫一個日志文件

#define _CRT_SECURE_NO_WARNINGS 1
#include
 
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;
 
}

文件內容:

#define定義標識符

舉例:

#define定義宏

#define 機制包括了一個規定,允許把參數替換到文本中,這種實現通常稱為宏(macro)或定義 宏(define macro)。

這個問題,的解決辦法是在宏定義表達式兩邊加上一對括號就可以了。

#define DOUBLE( x) ? ( ( x ) + ( x ) )

所以用于對數值表達式進行求值的宏定義都應該用這種方式加上括號,避免在使用宏時由于參數中 的操作符或鄰近操作符之間不可預料的相互作用。

#define 替換規則

在程序中擴展#define定義符號和宏時,需要涉及幾個步驟。

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

2. 替換文本隨后被插入到程序中原來文本的位置。對于宏,參數名被他們的值所替換。

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

注意:

1. 宏參數和#define 定義中可以出現其他#define定義的符號。但是對于宏,不能出現遞歸。 2. 當預處理器搜索#define定義的符號的時候,字符串常量的內容并不被搜索。

#和##兩個預處理的工具

#可以把參數插入到字符串中?

##可以把位于它兩邊的符號合成一個符號。 它允許宏定義從分離的文本片段創建標識符。?

帶副作用的宏參數

當宏參數在宏的定義中出現超過一次的時候,如果參數帶有副作用,那么你在使用這個宏的時候就可能 出現危險,導致不可預測的后果。副作用就是表達式求值的時候出現的永久性效果。

例如:

x+1;//不帶副作用

x++;//帶有副作用

宏和函數對比?

宏通常被應用于執行簡單的運算。 比如在兩個數中找出較大的一個。

#define MAX(a, b) ((a)>(b)?(a):(b))

?那為什么不用函數來完成這個任務?

原因有二:

1. 用于調用函數和從函數返回的代碼可能比實際執行這個小型計算工作所需要的時間更多。 所以宏比函數在程序的規模和速度方面更勝一籌。

2. 更為重要的是函數的參數必須聲明為特定的類型。 所以函數只能在類型合適的表達式上使用。反之這個宏怎可以適用于整形、長整型、浮點型等可以 用于>來比較的類型。

宏是類型無關的。

宏的缺點:

當然和函數相比宏也有劣勢的地方:

1. 每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序 的長度。

2. 宏是沒法調試的?

3. 宏由于類型無關,也就不夠嚴謹。

4. 宏可能會帶來運算符優先級的問題,導致程容易出現錯。

#undef移除宏

?這條指令用于移除一個宏定義。

#undef NAME //如果現存的一個名字需要被重新定義,那么它的舊名字首先要被移除。?

代碼如下:

命令行定義

許多C 的編譯器提供了一種能力,允許在命令行中定義符號。用于啟動編譯過程。 例如:當我們根據同一個源文件要編譯出不同的一個程序的不同版本的時候,這個特性有點用處。(假 定某個程序中聲明了一個某個長度的數組,如果機器內存有限,我們需要一個很小的數組,但是另外一 個機器內存大寫,我們需要一個數組能夠大寫。)

代碼如下:

在linux底下,通過gcc test.c -D M=100,下面的代碼就可以正常運行了

條件編譯?

滿足條件就編譯,不滿足條件就不參與編譯。

代碼如下:

?并不打印結果。?

#if 1為真,后面就編譯,如果為0就不編譯。?

頭文件包含?

‘頭文件包含的兩種方式:

#include"add.h" 本地文件包含,自定義的函數的頭文件用""

#include 庫文件包含,C語言庫中提供的函數的頭文件用<>

?查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找庫函數頭文件一樣在標 準位置查找頭文件。 如果找不到就提示編譯錯誤

查找頭文件直接去標準路徑下去查找,如果找不到就提示編譯錯誤。 這樣是不是可以說,對于庫文件也可以使用 “” 的形式包含? 答案是肯定的,可以。 但是這樣做查找的效率就低些,當然這樣也不容易區分是庫文件還是本地文件了。?

嵌套文件包含?

每個頭文件的開頭寫:

#ifndef __TEST_H__

#define __TEST_H__

//頭文件的內容

#endif ? //__TEST_H__

或者:

#pragma once

就可以避免頭文件的重復引入。?

總結

題主太累了,總結就不寫了,已經過了零點了,要睡覺了。實在是感覺頂不住了,明天還得早起呢,希望大家也能不忘初心,堅持學下去,大家一起努力!!!!!!?

原文鏈接:https://blog.csdn.net/m0_64397675/article/details/123034584

欄目分類
最近更新