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

C語(yǔ)言中的程序環(huán)境與預(yù)處理詳情

 更新時(shí)間:2022年07月08日 11:21:46   作者:烤雞肉玉米煎餅  
這篇文章主要介紹了C語(yǔ)言中的程序環(huán)境與預(yù)處理詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下

1.程序的翻譯環(huán)境和執(zhí)行環(huán)境

在ANSI C的任何一種實(shí)現(xiàn)中,存在兩個(gè)不同的環(huán)境

  • 第1種是翻譯環(huán)境,在這個(gè)環(huán)境中源代碼被轉(zhuǎn)換為可執(zhí)行的機(jī)器指令,也就是從,c文件到.exe文件;
  • 第2種是執(zhí)行環(huán)境,它用于實(shí)際執(zhí)行代碼;

翻譯環(huán)境是由編譯器提供的,而執(zhí)行環(huán)境是由操作系統(tǒng)提供的。

如MSVC,DEV C++,Codeblocks這些編譯軟件都是集成開發(fā)環(huán)境,也就是集成了編輯,編譯,鏈接和調(diào)試等功能。

2.詳解編譯和鏈接

2.1程序翻譯環(huán)境下的編譯和鏈接

從源文件到可執(zhí)行程序可以分為編譯和鏈接兩步,在編譯階段源文件變成了目標(biāo)文件,在鏈接階段目標(biāo)文件變成了可執(zhí)行程序。

組成程序的每個(gè)源文件通過編譯過程分別轉(zhuǎn)化成目標(biāo)文件;

每個(gè)目標(biāo)文件由鏈接器捆綁在一起,形成一個(gè)單一而完整的可執(zhí)行程序;

鏈接器同時(shí)也會(huì)引入標(biāo)準(zhǔn)C函數(shù)庫(kù)中任何被該程序所用到的函數(shù),而且鏈接器也可以搜索程序員個(gè)人的程序庫(kù),將其需要的函數(shù)也鏈接到程序中。

圖解:

2.2深入編譯和鏈接過程

編譯本身可以分為預(yù)編譯(預(yù)處理),編譯和匯編。

預(yù)編譯:在預(yù)編譯階段會(huì)將#include引用的頭文件給輸入到文件里面,進(jìn)行#define定義的標(biāo)識(shí)符的替換,以及將注釋給刪除,因?yàn)樽⑨屖墙o程序員看的,不是給電腦看的;

編譯:在這個(gè)過程中會(huì)將C語(yǔ)言代碼翻譯成匯編代碼,編譯器會(huì)對(duì)代碼進(jìn)行詞法分析,語(yǔ)法分析,語(yǔ)義分析,符號(hào)匯總;

匯編:會(huì)把在編譯階段形成的匯編代碼翻譯成二進(jìn)制的指令,并將匯總的符號(hào)形成一個(gè)符號(hào)表;

在編譯完成之后,就會(huì)開始鏈接,鏈接過程會(huì)合成段表,也就是將目標(biāo)文件捆綁在一起,以及將符號(hào)表合并并進(jìn)行重定位,最后生成可執(zhí)行程序。

2.3運(yùn)行環(huán)境

程序執(zhí)行的過程:

  • 1.程序必須載入內(nèi)存中。在有操作系統(tǒng)的環(huán)境中,一般這個(gè)過程由操作系統(tǒng)完成,在獨(dú)立的環(huán)境中,程序的載入必須由手工安排,也可能是通過可執(zhí)行代碼置入只讀內(nèi)存來完成。
  • 2.程序開始執(zhí)行,并調(diào)用main函數(shù)。
  • 3.開始執(zhí)行程序代碼,這個(gè)時(shí)候程序?qū)⑹褂靡粋€(gè)運(yùn)行時(shí)堆棧,存儲(chǔ)函數(shù)的局部變量和返回地址,程序同時(shí)也可以使用靜態(tài)內(nèi)存,存儲(chǔ)于靜態(tài)內(nèi)存中的變量在程序的整個(gè)執(zhí)行過程一種保留它們的值。
  • 4.終止程序。正常終止main函數(shù),也有可能是意外終止。

3.預(yù)處理詳解

3.1預(yù)定義符號(hào)

預(yù)定義符號(hào)都是語(yǔ)言內(nèi)置的

__FILE__       //進(jìn)行編譯的源文件

__LINE__      //當(dāng)前代碼的行號(hào)

__DATE__    //文件被編譯時(shí)的日期

__TIME__     //文件被編譯時(shí)的時(shí)間

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

預(yù)定義符號(hào)的使用:

int main()
{
	printf("file:%s\nline:%d\ndata:%s\ntime:%s\n", __FILE__, __LINE__, __DATE__, __TIME__);
	return 0;
}

3.2#define

3.2.1#define定義的標(biāo)識(shí)符

#define name stuff

舉例:

#define MAX 1000
#define reg register ?????//為 register這個(gè)關(guān)鍵字,創(chuàng)建一個(gè)簡(jiǎn)短的名字
#define do_forever for(;;) ??//用更形象的符號(hào)來替換一種實(shí)現(xiàn)
#define CASE break;case ????//在寫case語(yǔ)句的時(shí)候自動(dòng)把 break寫上。
// 如果定義的 stuff過長(zhǎng),可以分成幾行寫,除了最后一行外,每行的后面都加一個(gè)反斜杠(續(xù)行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t \
?????????????date:%s\ttime:%s\n" ,\
?????????????__FILE__,__LINE__ , ???\
?????????????__DATE__,__TIME__ )

在define定義標(biāo)識(shí)符的時(shí)候,不要在最后加上;

如下面這種情況,會(huì)出現(xiàn)語(yǔ)法錯(cuò)誤

#define NUM 100;
int main()
{
	int a = 0;
	if (1)
		a = NUM;
	else
		a = 0;
	return 0;
}

3.2.2#define定義宏

#define機(jī)制包括了一個(gè)規(guī)定,允許把參數(shù)替換到文本中,這種實(shí)現(xiàn)通常稱為宏或宏定義

宏的聲明方式如下:

#define name(parament-list) stuff

其中的parament-list是一個(gè)由逗號(hào)隔開的符號(hào)表,它們可能出現(xiàn)在stuff中

把name(parament-list)這個(gè)整體稱為宏

注意:

參數(shù)列表的左括號(hào)必須與name緊貼,如果兩者之間存在空格,參數(shù)列表就會(huì)被解釋為stuff的一部分,語(yǔ)法就是這么規(guī)定的。

接下來是宏的使用:

比如用宏實(shí)現(xiàn)一個(gè)數(shù)的平方:

#define SQUARE(n) n * n
int main()
{
	SQUARE(6);
	return 0;
}

語(yǔ)句SQUARE(6)就會(huì)替換成6 * 6;

解釋:宏先是接受一個(gè)參數(shù),SQRARE(n)中的n就變成了6,其后宏的內(nèi)容也就由n * n變成了6 * 6,再將6 * 6替換到程序中使用宏的位置。

但是,這個(gè)宏這么寫存在一個(gè)問題,如下代碼:

#define SQUARE(n) n * n
int main()
{
	printf("%d\n", SQUARE(1 + 3));
	return 0;
}

看上去似乎最后的結(jié)果是16,然而實(shí)際上參數(shù)n會(huì)被替換成1 + 3,這樣最終替換的內(nèi)容是1 + 3 * 1 + 3,這條表達(dá)式最終的結(jié)果是7.

所以需要在n的左右兩邊加上一對(duì)括號(hào),如下:

#define SQUARE(n) (n) * (n)

再看另外一個(gè)宏定義:

#define DOUBLE(n) (n) + (n)

代碼:

#define DOUBLE(n) (n) + (n)
int main()
{
	printf("%d\n", 5 * DOUBLE(3));
	return 0;
}

 看上去最終結(jié)果似乎是30,然而替換后語(yǔ)句實(shí)際上是

printf("%d\n", 5 * (3) + (3));

所以最終結(jié)果是18

所以為了保證獲得想要的結(jié)果,宏定義表達(dá)式兩邊還需要加上一對(duì)括號(hào)

#define DOUBLE ((n) + (n))

所以用于對(duì)數(shù)值表達(dá)式進(jìn)行求值的宏定義都應(yīng)該用這種方式加上括號(hào),避免在使用宏時(shí)由于參數(shù)中
的操作符或鄰近操作符之間產(chǎn)生不可預(yù)料的相互作用。 

3.2.3#define替換規(guī)則

在程序中擴(kuò)展#define定義符號(hào)和宏時(shí),需要涉及幾個(gè)步驟

  • 1.在調(diào)用宏時(shí),首先對(duì)宏括號(hào)中的參數(shù)進(jìn)行檢查,看看是否包含由#define定義的符號(hào),如果有,這些符號(hào)首先被替換。
  • 2.替換文本后,文本被插入到程序中原來文本的位置,對(duì)于宏,參數(shù)名被對(duì)應(yīng)的值所替換。
  • 3.最后,再次對(duì)結(jié)果文件進(jìn)行掃描,查看替換過后的內(nèi)容是否還有#define定義的符號(hào),如果有,則重復(fù)上述處理過程

注意:

  • 1.宏參數(shù)和#define定義中可以出現(xiàn)其他#define定義的符號(hào),但是對(duì)于宏,不能實(shí)現(xiàn)遞歸。
  • 2.當(dāng)預(yù)處理器搜索#define定義的符號(hào)時(shí),字符串常量的內(nèi)容并不被搜索。

比如:

#define a 123
int main()
{
	printf("%s", "a");
	return 0;
}

語(yǔ)句printf("%s", "a");中的a并不會(huì)被替換成123

3.3.4#和##

如何把參數(shù)插入到字符串中?

如下代碼:

int main()
{
	printf("abc""def");
	return 0;
}

輸出的結(jié)果是abcdef

發(fā)現(xiàn)字符串是有自動(dòng)相連的特點(diǎn)的

看下面這個(gè)代碼:

#define PRINT(FORMAT, VALUE) printf("the value of "#VALUE " is "FORMAT"\n", VALUE)
int main()
{
	int a = 6;
	PRINT("%d", a);
	return 0;
}

最終輸出的結(jié)果是:

the value of a is 6

所以#VALUE會(huì)被預(yù)處理器在預(yù)處理階段預(yù)處理為"VALUE"

接下來看看##的作用:

##可以把位于它兩邊的符號(hào)合成一個(gè)符號(hào),并且允許宏定義從分離的文本片段創(chuàng)建標(biāo)識(shí)符。

如下代碼:

#define MAXMIN 6
#define MIDDLE MAX##MIN
int main()
{
	printf("%d\n", MIDDLE);
	return 0;
}

注意:連接之后產(chǎn)生的符號(hào)必須是已經(jīng)定義的,否則結(jié)果就是非法的。

3.2.5帶副作用的宏參數(shù)

當(dāng)宏參數(shù)在宏的定義中出現(xiàn)超過一次的時(shí)候,如果參數(shù)帶有副作用,那么在使用這個(gè)宏的時(shí)候就可能會(huì)出現(xiàn)危險(xiǎn),導(dǎo)致不可預(yù)測(cè)的后果。副作用就是表達(dá)式求值的時(shí)候出現(xiàn)的永久性后果。

比如:

x + 1;//不帶有副作用

x++;  //帶有副作用

如下代碼可以證明副作用的宏參數(shù)帶來的問題

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d y=%d z=%d\n", x, y, z);
	return 0;
}

 在代碼預(yù)處理之后

z = ( (x++) > (y++) ? (x++) : (y++));

所以最終結(jié)果是x=6 y=10 z=9

3.2.6宏和函數(shù)對(duì)比

宏通常被用于執(zhí)行簡(jiǎn)單的運(yùn)算,比如在兩個(gè)數(shù)中找出較大的一個(gè)

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

對(duì)于為什么不用函數(shù)來完成這個(gè)任務(wù),有兩個(gè)原因:

  • 1.用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實(shí)際執(zhí)行這個(gè)小型計(jì)算工作所需要的時(shí)間多,所以此時(shí)宏在程序的規(guī)模和運(yùn)算方面更勝一籌。
  • 2.函數(shù)的參數(shù)必須聲明為特定的類型,所以函數(shù)只能在類型合適的表達(dá)式上使用,而宏可以適用于整型,浮點(diǎn)型,長(zhǎng)整型,宏是類型無關(guān)的。

當(dāng)然宏也是有缺點(diǎn)的:

  • 1.每次使用宏的時(shí)候,會(huì)將宏定義的代碼插入到程序中,除非宏比較短,否則可能大幅度增加程序的長(zhǎng)度;
  • 2.宏是沒法進(jìn)行調(diào)試的,因?yàn)樵陬A(yù)處理階段,宏定義的符號(hào)已經(jīng)發(fā)生了替換,此時(shí)調(diào)試看到的代碼和實(shí)際上運(yùn)行時(shí)的代碼是有所差異的;
  • 3.宏由于類型無關(guān),也就不夠嚴(yán)謹(jǐn)了;
  • 4.宏可能會(huì)帶來運(yùn)算級(jí)優(yōu)先的問題,導(dǎo)致程序容易出錯(cuò); 

宏有時(shí)候也可以做到函數(shù)做不到的事情,比如宏的參數(shù)可以出現(xiàn)類型,但是函數(shù)不行

如下代碼:

#define MALLOC(num, type)\
(type *)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//類型作為參數(shù)

 預(yù)處理替換后:

(int *)malloc(10 * sizeof(int));

會(huì)節(jié)省部分代碼。

總的來對(duì)比一下宏和函數(shù)的區(qū)別:

函數(shù)和宏的使用語(yǔ)法很相似,所以語(yǔ)言本身沒法幫助區(qū)分二者,所以平時(shí)的命名習(xí)慣是:

宏名全部大寫

函數(shù)名不要全部大寫

3.3#undef 

這條指令用于移除一個(gè)宏定義

語(yǔ)法:

#undef name

使用: 

#define MAX 5
int main()
{
	printf("%d\n", MAX);
#undef MAX
	return 0;
}

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ù)組能夠大些。)

#include <stdio.h>
int main()
{
  int array [ARRAY_SIZE];
  int i = 0;
  for(i = 0; i< ARRAY_SIZE; i ++)
 {
    array[i] = i;
 }
  for(i = 0; i< ARRAY_SIZE; i ++)
 {
    printf("%d " ,array[i]);
 }
  printf("\n" );
  return 0;
}

3.5條件編譯

在編譯一個(gè)程序的時(shí)候如果要將一條語(yǔ)句或者一組語(yǔ)句編譯或者放棄掉是很方便的,因?yàn)橛幸粋€(gè)叫條件編譯的東西。

對(duì)于調(diào)試性的代碼,刪除比較可惜,保留又會(huì)礙事,所以可以選擇性的去編譯。

如下代碼:

#define __DEBUG__
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d ", arr[i]);//為了觀察數(shù)組是否被賦值成功
#endif
	}
	return 0;
}

1.#if 常量表達(dá)式
        //...
#endif
//常量表達(dá)式由預(yù)處理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
        //..
#endif

2.多個(gè)分支的條件編譯
#if 常量表達(dá)式
        //...
#elif 常量表達(dá)式
        //...
#else
        //...
#endif

3.判斷是否被定義
#if defined(symbol)
#ifdef symbol

#if !defined(symbol)
#ifndef symbo

4.嵌套指令
#if defined(OS_UNIX)
        #ifdef OPTION1
                unix_version_option1();
        #endif
        #ifdef OPTION2
                unix_version_option2();
        #endif
#elif defined(OS_MSDOS)
        #ifdef OPTION2
                msdos_version_option2();
        #endif
#endif

3.6文件包含

#include指令可以使另一個(gè)文件被編譯,會(huì)讓被包含的頭文件出現(xiàn)在#include指令的地方

這種替換的方式很簡(jiǎn)單,預(yù)處理器會(huì)先刪除這條指令,并用包含文件里的內(nèi)容進(jìn)行替換,如果這個(gè)文  件被包含了10次,那實(shí)際上就會(huì)被編譯10次

3.6.1頭文件被包含的方式

本地文件包含:

#include "fliename.h"

查找方法:先在源文件的目錄下去查找,如果該頭文件未被找到,編譯器就會(huì)像去查找?guī)旌瘮?shù)的頭文件一樣在標(biāo)準(zhǔn)位置去查找頭文件,如果還找不到就提示編譯錯(cuò)誤。

庫(kù)文件包含:

#include <filename.h>

查找方法:直接在標(biāo)準(zhǔn)路徑下去查找,如果找不到就提示編譯錯(cuò)誤。

雖然可以對(duì)庫(kù)文件也采用""的包含方式,但是當(dāng)目錄下的文件非常多的時(shí)候,這樣查找起來的效率就會(huì)低一些了,而且也不容易去區(qū)分是庫(kù)文件還是頭文件了。

3.6.2嵌套文件包含

如圖:

  • common.h和common.c是公共模塊
  • test1.h和test1.c使用了公共模塊
  • test2.h和test2.c使用了公共模塊
  • test.h和test.c使用了test1模塊和test2模塊

這樣最終的程序中就會(huì)包含兩次common.h了,等于有2份common.h的內(nèi)容,會(huì)造成代碼的重復(fù)。

對(duì)此可以采用條件編譯的方式來解決這個(gè)問題

在引用每個(gè)頭文件時(shí)在開頭寫上這么一個(gè)內(nèi)容:

#ifndef __STDIO_H__
#define __STDIO_H__
#include <stdio.h>
#endif

如果沒有定義標(biāo)識(shí)符__STDIO_H__就定義__STDIO_H__并且去包含頭文件

如果下次還遇到包含頭文件的代碼,由于__STDUI_H__已經(jīng)被定義過,所以也就不會(huì)進(jìn)行第二次包含了

或者對(duì)于在頭文件的開頭也可以這么寫:

#pragma once
#include <stdio.h>

也可以避免頭文件的重復(fù)引入

4.其他預(yù)處理指令

比如:

#error

#pragma

#pragma

#line

到此這篇關(guān)于C語(yǔ)言中的程序環(huán)境與預(yù)處理詳情的文章就介紹到這了,更多相關(guān)C語(yǔ)言預(yù)處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于C++編寫一個(gè)Json解析器

    基于C++編寫一個(gè)Json解析器

    這篇文章主要為大家詳細(xì)介紹了如何基于C++編寫一個(gè)Json解析器,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解一下
    2023-03-03
  • 希爾排序算法的C語(yǔ)言實(shí)現(xiàn)示例

    希爾排序算法的C語(yǔ)言實(shí)現(xiàn)示例

    這篇文章主要介紹了希爾排序算法的C語(yǔ)言實(shí)現(xiàn)示例,希爾排序可以看作為一種高級(jí)的插入排序,需要的朋友可以參考下
    2016-04-04
  • C++ 中virtual 虛函數(shù)用法深入了解

    C++ 中virtual 虛函數(shù)用法深入了解

    這篇文章主要介紹了C++ 中virtual 虛函數(shù)用法深入了解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • C語(yǔ)言實(shí)現(xiàn)大數(shù)值金額大寫轉(zhuǎn)換的方法詳解

    C語(yǔ)言實(shí)現(xiàn)大數(shù)值金額大寫轉(zhuǎn)換的方法詳解

    這篇文章主要為大家詳細(xì)介紹了如何利用C語(yǔ)言實(shí)現(xiàn)大數(shù)值金額大寫轉(zhuǎn)換的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下
    2023-03-03
  • 詳細(xì)聊一聊algorithm中的排序算法

    詳細(xì)聊一聊algorithm中的排序算法

    <algorithm>是C++標(biāo)準(zhǔn)程序庫(kù)中的一個(gè)頭文件,定義了C++?STL標(biāo)準(zhǔn)中的基礎(chǔ)性的算法(均為函數(shù)模板),下面這篇文章主要給大家介紹了關(guān)于algorithm中排序算法的相關(guān)資料,需要的朋友可以參考下
    2022-06-06
  • C/C++實(shí)現(xiàn)詞法分析程序的示例代碼

    C/C++實(shí)現(xiàn)詞法分析程序的示例代碼

    這篇文章主要為大家詳細(xì)介紹了如何基于C/C++實(shí)現(xiàn)一個(gè)簡(jiǎn)單的詞法分析程序,并通過完成詞法分析程序,了解詞法分析的過程,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)學(xué)習(xí)
    2023-05-05
  • c++ bitset詳解

    c++ bitset詳解

    這篇文章主要介紹了C++ bitset用法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08
  • 新手socket編程入門詳解指南

    新手socket編程入門詳解指南

    本文,將一步一步引導(dǎo)初學(xué)者來學(xué)習(xí)socket,所有編程思路都結(jié)合在socket API里面,以及提供socket的疑問和基礎(chǔ)知識(shí)點(diǎn),同時(shí)在最后給出多個(gè)例程,下面可以和小編一起學(xué)習(xí)
    2019-05-05
  • 詳解C++ sort函數(shù)的cmp參數(shù)

    詳解C++ sort函數(shù)的cmp參數(shù)

    這篇文章主要介紹了C++ sort函數(shù)的cmp參數(shù),以升降排序個(gè)結(jié)構(gòu)體的排序展開的話題,感興趣的小伙伴可以參考下面文章內(nèi)容
    2021-09-09
  • 詳解C++何時(shí)需要拷貝構(gòu)造函數(shù)

    詳解C++何時(shí)需要拷貝構(gòu)造函數(shù)

    拷貝構(gòu)造函數(shù)是一個(gè)特殊的構(gòu)造函數(shù),用于創(chuàng)建一個(gè)新對(duì)象,該對(duì)象與另一個(gè)同類對(duì)象具有相同的屬性和值,在 C++ 中,拷貝構(gòu)造函數(shù)通常采用另一個(gè)同類對(duì)象作為參數(shù),并使用該對(duì)象初始化新對(duì)象,本文給大家講講何時(shí)需要拷貝函數(shù),需要的朋友可以參考下
    2023-09-09

最新評(píng)論