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

一起來(lái)學(xué)習(xí)C語(yǔ)言的程序環(huán)境與預(yù)處理

 更新時(shí)間:2022年03月24日 10:42:33   作者:Green_756  
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言程序環(huán)境與預(yù)處理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助

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

要支持c語(yǔ)言的實(shí)現(xiàn),會(huì)有不同的編譯器出現(xiàn),而這些編譯器都要遵循ANSI C,都存在兩種環(huán)境

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

.obj為后綴的就是目標(biāo)文件

而一個(gè)項(xiàng)目中可能會(huì)有很多.c后綴的源文件,分別處理后每經(jīng)過(guò)編譯器單獨(dú)處理,然后會(huì)生成對(duì)應(yīng)的目標(biāo)文件(.obj),然后總體經(jīng)過(guò)連接器處理,最終變成可執(zhí)行程序。

目標(biāo)文件最后還要加上鏈接庫(kù)整體一起通過(guò)鏈接器鏈接,變成可執(zhí)行程序.

鏈接庫(kù):在編寫(xiě)代碼的時(shí)候,會(huì)有一些不屬于我們自己寫(xiě)的函數(shù)(如printf),這些函數(shù)是自帶的庫(kù)里面包含的,這些庫(kù)就叫鏈接庫(kù)

(補(bǔ)函數(shù)的聲明與定義里面的靜態(tài)庫(kù))

從源文件生成可執(zhí)行程序的這一個(gè)過(guò)程就叫做翻譯環(huán)境

2.gcc C語(yǔ)言編譯器來(lái)演示編譯過(guò)程

2.1編譯

預(yù)編譯→編譯→匯編

預(yù)編譯(預(yù)處理):

文本操作:

1.頭文件的包含,#include——預(yù)編譯指令,將包含的頭文件給展開(kāi)

2.刪除注釋?zhuān)ㄗ⑨尡豢崭裉鎿Q)

3.#define定義符號(hào)的替換

2.2編譯:

生成.s的文件

把c語(yǔ)言代碼轉(zhuǎn)換成匯編代碼

1.語(yǔ)法分析

2.詞法分析

3.語(yǔ)義分析

4.符號(hào)匯總——匯總的是全局符號(hào)

《程序員的自我修養(yǎng)》——通俗地講解代碼編譯過(guò)程的細(xì)節(jié)

匯編:

生成了test.o

把匯編代碼轉(zhuǎn)換成二進(jìn)制指令

形成符號(hào)表:

框內(nèi)是十六進(jìn)制是地址

鏈接:

最終將.o文件鏈接成.exe可執(zhí)行程序

1.合并段表

2.符號(hào)表的合并和重定位(像Add一開(kāi)始地址為默認(rèn)0和另一個(gè).c文件內(nèi)的Add地址的為0x200,會(huì)重新定位)

符號(hào)表的意義:多個(gè)目標(biāo)文件進(jìn)行鏈接的時(shí)候會(huì)通過(guò)符號(hào)表查看來(lái)自外部的符號(hào)是否真實(shí)存在

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

1.程序必須載入內(nèi)存中。在有操作系統(tǒng)的環(huán)境中:一般這個(gè)由操作系統(tǒng)完成。在獨(dú)立的環(huán)境中,程序的載入必須由手工安排(電焊好伐),也可能是通過(guò)可執(zhí)行代碼置入只讀內(nèi)存來(lái)完成。

2.程序的執(zhí)行便開(kāi)始。接著便調(diào)用main函數(shù)。

3.開(kāi)始執(zhí)行程序代碼。這個(gè)時(shí)候程序?qū)⑹褂靡粋€(gè)運(yùn)行時(shí)堆棧(stack)(也就是之前博客中寫(xiě)到的函數(shù)棧幀的創(chuàng)建與銷(xiāo)毀),存儲(chǔ)函數(shù)的局部變量和返回地址。程序同時(shí)也可以使用靜態(tài)(static)內(nèi)存,存儲(chǔ)于靜態(tài)內(nèi)存中的變量在程序的整個(gè)執(zhí)行過(guò)程一直保留他們的值。

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

3詳解預(yù)處理

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

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

作用:記錄日志:可記錄在哪個(gè)文件在哪個(gè)日期在什么時(shí)候在哪個(gè)文件在哪一行

#define _CRT_sECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
    printf("%s\n", __FILE__);
    printf("%s\n", __TIME__);
    printf("%d\n", __LINE__);
    printf("%s\n", __DATE__);
    return 0;
}

預(yù)處理符號(hào)的應(yīng)用:

//預(yù)處理符號(hào)的應(yīng)用——寫(xiě)日志
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
    int i = 0;
    FILE* pf = fopen("test.txt", "w");
    if (NULL == pf)
    {
        printf("error is%s\n", strerror(errno));
        return 0;
    }
    for (i = 0; i < 5; i++)
    {
        fprintf(pf, "%s\t%s\t%s\t%d\ti=%d\n", __FILE__, __DATE__, __TIME__, __LINE__, i);
    }
    fclose(pf);
    pf = NULL;
    return 0;
}

3.2#define

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

兩種用法:

#define MM 100
#define reg register——關(guān)鍵字替換
 

#define末尾有時(shí)候可以加分號(hào)有時(shí)又不可以加上分號(hào),

不可以加上分號(hào)的情況:

//不可以加上分號(hào)的情況
#define _CRT_SECURE_NO_WARNINGS 1
#define MAX(x,y) ((x)>(y)?x:y);
#include <stdio.h>
int main()
{
    int a = 5;
    int b = 3;
    printf("%d\n", MAX(a, b));
    return 0;
}

因?yàn)榧由戏痔?hào)會(huì)使得宏在替換的時(shí)候也帶上分號(hào),所以在調(diào)用在一些函數(shù)內(nèi)部的時(shí)候會(huì)出現(xiàn)錯(cuò)誤。

綜上,當(dāng)我們定義宏的時(shí)候,最好不要加分號(hào)在末尾。

3.2.2 #define定義宏

這里也是將全部參數(shù)給替換掉,在預(yù)處理的時(shí)候就替換掉了,不信的話(huà)可以在解決方案處右擊,點(diǎn)擊屬性后選擇預(yù)處理,然后就可以在debug里面發(fā)現(xiàn)又應(yīng)該.i文件,點(diǎn)開(kāi)后就可以發(fā)現(xiàn)這里已經(jīng)被替換掉了。

#define Max(x,y) ((x)>(y)?(x):(y))
//      Max->宏的名字  
//         x和y->宏的參數(shù)
//                ((x)>(y)?(x):(y))->宏的內(nèi)容

ps:在定義宏的內(nèi)容的時(shí)候,最好每個(gè)參數(shù)都要加上小括號(hào),然后最后整體加上小括號(hào),否則如果傳入?yún)?shù)不是單獨(dú)一個(gè)值而是表達(dá)式的時(shí)候,會(huì)產(chǎn)生一些沒(méi)有意料到的優(yōu)先級(jí)計(jì)算改變

Tips:宏后面的參數(shù)的小括號(hào)一定要緊挨著宏的名

3.2.3 #define替換規(guī)則

1.先看宏的參數(shù)內(nèi)是不是有define的符號(hào),優(yōu)先替換掉define符號(hào)

2.對(duì)于宏,參數(shù)名被他們的值替換

注意?。?/strong>

1.宏的參數(shù)里可以出現(xiàn)其他#define定義的符號(hào),但不可以遞歸

2.當(dāng)define掃描預(yù)處理時(shí),字符串常量的內(nèi)容并不被搜索(也就是說(shuō)字符串里面的東西是不會(huì)被宏預(yù)處理的)

3.2.4 #和##

#

相當(dāng)于把宏的參數(shù)放進(jìn)字符串中變成所對(duì)應(yīng)的字符串

// #的用法
#define _CRT_SECURE_NO_WARNINGS 1
#define print(x) printf("the value of " #x " is %d\n",x)
#include <stdio.h>
int main()
{
    int a = 5;
    int b = 4;
    print(a);
    print(b);
    return 0;
}

##

可以把兩邊分離片段合成一個(gè)符號(hào)

#define CAT(C,num) C##num
int main()
{
    int Class104=10000;
    printf("%d\n",CAT(Class,104));
    return 0;
}

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

#define MAX(x,y) ((x)>(y)?(x):(y))
int main()
{
    int a=3;
    int b=5;
    int m=MAX(a++,b++);//宏的參數(shù)是直接替換進(jìn)去
    所以替換完之后為:
    int m=((a++)>(b++)?(a++):(b++));//會(huì)出現(xiàn)錯(cuò)誤
    printf("%d\n",m);
    printf("%d %d\n",a,b);
    return 0;
}

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

宏的優(yōu)點(diǎn):

1.用于調(diào)用函數(shù)和從函數(shù)返回的代碼可能比實(shí)際執(zhí)行這個(gè)小型計(jì)算工作所需要的時(shí)間更多。 所以宏比函數(shù)在程序的規(guī)模和速度方面更勝一籌

2.更為重要的是函數(shù)的參數(shù)必須聲明為特定的類(lèi)型。 所以函數(shù)只能在類(lèi)型合適的表達(dá)式上使用。反之這個(gè)宏怎可以適用于整形、長(zhǎng)整型、浮點(diǎn)型等可以 用于>來(lái)比較的類(lèi)型。

宏的缺點(diǎn):

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

2.宏是沒(méi)法調(diào)試的。

3.宏由于類(lèi)型無(wú)關(guān),也就不夠嚴(yán)謹(jǐn)。

4.宏可能會(huì)帶來(lái)運(yùn)算符優(yōu)先級(jí)的問(wèn)題,導(dǎo)致程容易出現(xiàn)錯(cuò)

可從以下方面比較宏與函數(shù)的區(qū)別:

  • 代碼長(zhǎng)度——宏如果不是特別小的話(huà),每一次使用的時(shí)候都要替換成宏的定義,可能會(huì)導(dǎo)致最終代碼特別長(zhǎng),大幅增長(zhǎng)程序長(zhǎng)度,而函數(shù)每次都只調(diào)用那一段代碼
  • 執(zhí)行速度——宏只需要執(zhí)行一行的代碼,而函數(shù)擁有調(diào)用函數(shù),執(zhí)行代碼,返回參數(shù)這三步操作,所以相對(duì)來(lái)說(shuō)會(huì)慢一些
  • 操作符優(yōu)先級(jí)——由于宏是不經(jīng)過(guò)計(jì)算直接將參數(shù)傳進(jìn)去的,所以在傳參后可能會(huì)有優(yōu)先級(jí)的不同導(dǎo)致結(jié)果與我們想要的最終結(jié)果有出入,除非加上括號(hào)。相對(duì)的函數(shù)參數(shù)只在函數(shù)調(diào)用的時(shí)候求值一次,會(huì)比較容易猜測(cè)結(jié)果。
  • 帶有副作用的參數(shù)——參數(shù)可能被替換到宏體中的多個(gè)位置,所以帶有副作用的參數(shù)求值可能會(huì)產(chǎn)生不可預(yù)料的結(jié)果。函數(shù)參數(shù)只在傳參的時(shí)候求值一次,結(jié)果更容易控制。
  • 參數(shù)類(lèi)型——宏的參數(shù)類(lèi)型相對(duì)自由,只要對(duì)參數(shù)的操作合法,就可以任何只要符合規(guī)定的參數(shù)類(lèi)型。函數(shù)的參數(shù)類(lèi)型是固定死的。
  • 調(diào)試——宏不方便調(diào)試。函數(shù)能夠調(diào)試。
  • 遞歸——宏不能夠遞歸,而函數(shù)可以遞歸

3.2.7 命名的約定

一般來(lái)講宏與函數(shù)的使用語(yǔ)法很類(lèi)似,所以以后使用這種方法區(qū)分宏與函數(shù):

  • 宏名全部大寫(xiě)
  • 函數(shù)名不要全部大寫(xiě)(可開(kāi)頭或部分大寫(xiě))

3.3 undef

去除一個(gè)宏定義

#undef 宏名

3.4命令行定義

許多C 的編譯器提供了一種能力,允許在命令行中定義符號(hào)。用于啟動(dòng)編譯過(guò)程。 例如:當(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 [SZ];
    int i = 0;
    for(i = 0; i< SZ; i ++)
   {
        array[i] = i;
   }
    for(i = 0; i< SZ; i ++)
   {
        printf("%d " ,array[i]);
   }
    printf("\n" );
    return 0;
}
 

在這里我們可以知道SZ這個(gè)符號(hào)始終沒(méi)有被定義,到這里為止我們的程序還是無(wú)法運(yùn)行的,會(huì)報(bào)錯(cuò),但是:

編譯指令:

//linux 環(huán)境演示
gcc -D SZ=10 programe.c

在編譯完這一行以后程序就能夠執(zhí)行了,這是因?yàn)槲覀冊(cè)诿钚兄幸呀?jīng)將SZ這個(gè)符號(hào)定義好了。

3.5 條件編譯

有一段代碼,編譯了麻煩,刪去了可惜,這時(shí)可以選擇是否編譯,這時(shí)候就要用到條件編譯。

應(yīng)用場(chǎng)景:當(dāng)我們使用在不同系統(tǒng)時(shí),比如在用到windows系統(tǒng)時(shí)我們需要用到這一段代碼,而在Linus系統(tǒng)上又要用到另一段代碼而不能用windows那段代碼的時(shí)候,不可以刪除,因?yàn)橐獙?shí)現(xiàn)一個(gè)程序的跨平臺(tái)使用,這時(shí)候就需要用到條件編譯來(lái)選擇什么時(shí)候使用哪段代碼。

Tips:我們要明確條件編譯指令也是預(yù)處理指令

//條件編譯
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
#if 1 //這里的常量非0,于是執(zhí)行,如果為0,則不執(zhí)行
        printf("%d\n", i);
#endif
        return 0;
    }
}

不能放變量,因?yàn)槭穷A(yù)處理階段執(zhí)行的,而變量在預(yù)處理中還沒(méi)有出現(xiàn),所以我們只能放常量進(jìn)去,否則放變量進(jìn)去的話(huà)只能判定為0。

常見(jiàn)的條件編譯指令:

1.#if 常量/常量表達(dá)式     #endif

2.#if 常量表達(dá)式 #elif 常量表達(dá)式 #else 常量表達(dá)式    #endif

3.判斷是否被定義:只要你定義了宏就為真

  • #if defined(宏名) ...#endif #ifdef 宏名 ... #endif
  • 如果要做到的是沒(méi)有定義,則在前面加上!

#if !defined(宏名) ...#endif             #ifdef !宏名 ... #endif

4.嵌套指令

//全部類(lèi)型的條件編譯
#define _CRT_SECURE_NO_WARNINGS 1
#define VALUE 200
#define TEST 20
#include <stdio.h>
int main()
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
#if 1
        printf("%d\n", i);
#endif
    }
#if VALUE<100
    printf("value<100\n");
#elif VALUE>=100&&VALUE<=150
    printf("value>=100且value<=150\n");
#else
    printf("value>150\n");
#endif
#ifdef VALUE
    printf("VALUE已定義\n");
#else
    printf("VALUE未定義\n");
#endif
#if defined(VALUES)
    printf("VALUES已定義\n");
#else
    printf("VALUES未定義\n");
#endif
    //嵌套指令
#if VALUE<=150
#if defined(VALUE)
    printf("VALUE小于或等于150,VALUE已定義\n");
#else
    printf("VALUE小于或等于150,VALUE未定義\n");
#endif
#elif VALUE>150&&VALUE<=200
#ifdef TEST
    printf("VALUE大于150且小于等于200,TEST已定義\n");
#else
    printf("VALUE大于150且小于等于200,TEST未定義\n");
#endif
#endif
    return 0;
}

3.6文件包含

我們知道在預(yù)編譯時(shí)會(huì)包含頭文件,而頭文件例如<stdio.h>從預(yù)編譯.i文件上看我們可以知道有2000多行代碼,若真的重復(fù)包含了五六次,則代碼量直接上升到了一萬(wàn)多行,這時(shí)候就會(huì)使得代碼過(guò)于冗長(zhǎng),同時(shí)也占用很多內(nèi)存,這個(gè)時(shí)候我們就需要文件包含來(lái)確認(rèn)是否重復(fù)包含了同一個(gè)頭文件。

方法一:

//在頭文件中
#ifndef __TEST_H__
#define __TEST_H__
int Add(int x,int y);
#endif

方法二:

//在頭文件中
#pragma once
int Add(int x,int y);
 

ps:這個(gè)#pragma once是比較高級(jí)的寫(xiě)法,在遠(yuǎn)古編譯器里面是無(wú)法使用的(如vc)

3.6.1頭文件被包含的方式

在小綠本人之前的三子棋以及掃雷的博客中都有自己創(chuàng)造頭文件而我們?cè)谝米约簞?chuàng)造的頭文件時(shí)是以#include "fun.h" 這樣的形式引用的,但是在引用庫(kù)函數(shù)時(shí)確實(shí)以#include <stdio.h>的方式引用,那么用不同的符號(hào)引用頭文件有什么不一樣呢?

""的查找策略

""的查找策略是:先在源文件所在的目錄下查找,如果沒(méi)有找到,編譯器就像查找?guī)旌瘮?shù)頭文件一樣在標(biāo)準(zhǔn)位置查找頭文件。再如果找不到就提示編譯錯(cuò)誤。

<>的查找策略

查找頭文件直接去標(biāo)準(zhǔn)位置下查找,如果找不到就直接提示編譯錯(cuò)誤。

總結(jié)

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

相關(guān)文章

最新評(píng)論