版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
專注于為中小企業(yè)提供網(wǎng)站制作、做網(wǎng)站服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)文縣免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了1000多家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。首先指出一點(diǎn),我們通常所說的編譯器并非僅指編譯器,確切來說是編譯工具鏈,里面包括了預(yù)編譯器、編譯器、匯編器和連接器。
對(duì)于外部函數(shù)實(shí)體(處于調(diào)用函數(shù)所在源文件之外的其他源文件中的函數(shù)),是在鏈接過程中,才會(huì)被尋找和添加進(jìn)程序,一旦沒有找到函數(shù)實(shí)體,就會(huì)報(bào)錯(cuò),無法成功鏈接。
而外部函數(shù)的聲明(一般聲明在頭文件中)只是令程序順利通過編譯而已,此時(shí)并不需要搜索到外部函數(shù)的實(shí)體。
當(dāng)然,外部函數(shù)實(shí)體所在源文件也需要被編譯為目標(biāo)文件,至于鏈接時(shí) 如何找到該函數(shù)實(shí)體,這由鏈接器完成。
另外,頭文件和對(duì)應(yīng)源文件的命名是沒有任何必然聯(lián)系的。
下面在網(wǎng)上找了一篇博文:
簡單的說其實(shí)要理解C文件與頭文件(即.h)有什么不同之處,首先需要弄明白編譯器的工作過程,一般說來編譯器會(huì)做以下幾個(gè)過程:
1.預(yù)處理階段
2.詞法與語法分析階段
3.編譯階段,首先編譯成純匯編語句,再將之匯編成跟CPU相關(guān)的二進(jìn)制碼,生成各個(gè)目標(biāo)文件 (.obj文件)
4.連接階段,將各個(gè)目標(biāo)文件中的各段代碼進(jìn)行絕對(duì)地址定位,生成跟特定平臺(tái)相關(guān)的可執(zhí)行文件,當(dāng)然,最后還可以用objcopy生成純二進(jìn)制碼,也就是去掉了文件格式信息。(生成.exe文件)
編譯器在編譯時(shí)是以C文件為單位進(jìn)行的,也就是說如果你的項(xiàng)目中一個(gè)C文件都沒有,那么你的項(xiàng)目將無法編譯,連接器是以目標(biāo)文件為單位,它將一個(gè)或多個(gè)目標(biāo)文件進(jìn)行函數(shù)與變量的重定位,生成最終的可執(zhí)行文件,在PC上的程序開發(fā),一般都有一個(gè)main函數(shù),這是各個(gè)編譯器的約定,當(dāng)然,你如果自己寫連接器腳本的話,可以不用main函數(shù)作為程序入口?。。?!
(main .c文件 目標(biāo)文件 可執(zhí)行文件 )
有了這些基礎(chǔ)知識(shí),再言歸正傳,為了生成一個(gè)最終的可執(zhí)行文件,就需要一些目標(biāo)文件,也就是需要C文件,而這些C文件中又需要一個(gè)main函數(shù)作為可執(zhí)行程序的入口,那么我們就從一個(gè)C文件入手,假定這個(gè)C文件內(nèi)容如下:
#include
#include "mytest.h"
int main(int argc,char **argv)
{
test = 25;
printf("test.................%d/n",test);
}
頭文件內(nèi)容如下:
int test;
現(xiàn)在以這個(gè)例子來講解編譯器的工作:
1.預(yù)處理階段:編譯器以C文件作為一 個(gè)單元,首先讀這個(gè)C文件,發(fā)現(xiàn)第一句與第二句是包含一個(gè)頭文件,就會(huì)在所有搜索路徑中尋找這兩個(gè)文件,找到之后,就會(huì)將相應(yīng)頭文件中再去處理宏,變量, 函數(shù)聲明,嵌套的頭文件包含等,檢測依賴關(guān)系,進(jìn)行宏替換,看是否有重復(fù)定義與聲明的情況發(fā)生,最后將那些文件中所有的東東全部掃描進(jìn)這個(gè)當(dāng)前的C文件 中,形成一個(gè)中間“C文件”
2.編譯階段,在上一步中相當(dāng)于將那個(gè)頭文件中的test變量掃描進(jìn)了一個(gè)中 間C文件,那么test變量就變成了這個(gè)文件中的一個(gè)全局變量,此時(shí)就將所有這個(gè)中間C文件的所有變量,函數(shù)分配空間,將各個(gè)函數(shù)編譯成二進(jìn)制碼,按照特 定目標(biāo)文件格式生成目標(biāo)文件,在這種格式的目標(biāo)文件中進(jìn)行各個(gè)全局變量,函數(shù)的符號(hào)描述,將這些二進(jìn)制碼按照一定的標(biāo)準(zhǔn)組織成一個(gè)目標(biāo)文件
3.連接階段,將上一步成生的各個(gè)目標(biāo)文件,根據(jù)一些參數(shù),連接生成最終的可 執(zhí)行文件,主要的工作就是重定位各個(gè)目標(biāo)文件的函數(shù),變量等,相當(dāng)于將個(gè)目標(biāo)文件中的二進(jìn)制碼按一定的規(guī)范合到一個(gè)文件中再回到C文件與頭文件各寫什么內(nèi) 容的話題上:理論上來說C文件與頭文件里的內(nèi)容,只要是C語言所支持的,無論寫什么都可以的,比如你在頭文件中寫函數(shù)體,只要在任何一個(gè)C文件包含此頭文 件就可以將這個(gè)函數(shù)編譯成目標(biāo)文件的一部分(編譯是以C文件為單位的,如果不在任何C文件中包含此頭文件的話,這段代碼就形同虛設(shè)),你可以在C文件中進(jìn) 行函數(shù)聲明,變量聲明,結(jié)構(gòu)體聲明,這也不成問題?。?!那為何一定要分成頭文件與C文件呢?又為何一般都在頭件中進(jìn)行函數(shù),變量聲明,宏聲明,結(jié)構(gòu)體聲明 呢?而在C文件中去進(jìn)行變量定義,函數(shù)實(shí)現(xiàn)呢??原因如下:
1.如果在頭文件中實(shí)現(xiàn)一個(gè)函數(shù)體,那么如果在多個(gè)C文件中引用它,而且又同時(shí)編 譯多個(gè)C文件,將其生成的目標(biāo)文件連接成一個(gè)可執(zhí)行文件,在每個(gè)引用此頭文件的C文件所生成的目標(biāo)文件中,都有一份這個(gè)函數(shù)的代碼,如果這段函數(shù)又沒有定 義成局部函數(shù),那么在連接時(shí),就會(huì)發(fā)現(xiàn)多個(gè)相同的函數(shù),就會(huì)報(bào)錯(cuò)
2.如果在頭文件中定義全局變量,并且將此全局變量賦初值,那么在多個(gè)引用此 頭文件的C文件中同樣存在相同變量名的拷貝,關(guān)鍵是此變量被賦了初值,所以編譯器就會(huì)將此變量放入DATA段,最終在連接階段,會(huì)在DATA段中存在多個(gè) 相同的變量,它無法將這些變量統(tǒng)一成一個(gè)變量,也就是僅為此變量分配一個(gè)空間,而不是多份空間,假定這個(gè)變量在頭文件沒有賦初值,編譯器就會(huì)將之放入 BSS段,連接器會(huì)對(duì)BSS段的多個(gè)同名變量僅分配一個(gè)存儲(chǔ)空間
3.如果在C文件中聲明宏,結(jié)構(gòu)體,函數(shù)等,那么我要在另一個(gè)C文件中引用相 應(yīng)的宏,結(jié)構(gòu)體,就必須再做一次重復(fù)的工作,如果我改了一個(gè)C文件中的一個(gè)聲明,那么又忘了改其它C文件中的聲明,這不就出了大問題了,程序的邏輯就變成 了你不可想象的了,如果把這些公共的東東放在一個(gè)頭文件中,想用它的C文件就只需要引用一個(gè)就OK了?。。∵@樣豈不方便,要改某個(gè)聲明的時(shí)候,只需要?jiǎng)右?下頭文件就行了
4.在頭文件中聲明結(jié)構(gòu)體,函數(shù)等,當(dāng)你需要將你的代碼封裝成一個(gè)庫,讓別人來用你的代碼,你又不想公布源碼,那么人家如何利 用你的庫呢?也就是如何利用你的庫中的各個(gè)函數(shù)呢??一種方法是公布源碼,別人想怎么用就怎么用,另一種是提供頭文件,別人從頭文件中看你的函數(shù)原型,這 樣人家才知道如何調(diào)用你寫的函數(shù),就如同你調(diào)用printf函數(shù)一樣,里面的參數(shù)是怎樣的??你是怎么知道的??還不是看人家的頭文件中的相關(guān)聲明 ?。。?!當(dāng)然這些東東都成了C標(biāo)準(zhǔn),就算不看人家的頭文件,你一樣可以知道怎么使用
c語言中.c和.h文件的困惑
本質(zhì)上沒有任何區(qū)別。 只不過一般:
.h文件是頭文件,內(nèi)含函數(shù)聲明、宏定義、結(jié)構(gòu)體定義等內(nèi)容.c文件是程序文件,內(nèi)含函數(shù)實(shí)現(xiàn),變量定義等內(nèi)容。而且是什么后綴也沒有關(guān)系,只不過編譯器會(huì)默認(rèn)對(duì)某些后綴的文件采取某些動(dòng)作。你可以強(qiáng)制編譯器把任何后綴的文件都當(dāng)作c文件來編。
這樣分開寫成兩個(gè)文件是一個(gè)良好的編程風(fēng)格。
而且,比方說 我在aaa.h里定義了一個(gè)函數(shù)的聲明,然后我在aaa.h的同一個(gè)目錄下建立aaa.c , aaa.c里定義了這個(gè)函數(shù)的實(shí)現(xiàn),然后是在main函數(shù)所在.c文件里#include這個(gè)aaa.h 然后我就可以使用這個(gè)函數(shù)了。 main在運(yùn)行時(shí)就會(huì)找到這個(gè)定義了這個(gè)函數(shù)的aaa.c文件。這是因?yàn)椋簃ain函數(shù)為標(biāo)準(zhǔn)C/C++的程序入口,編譯器會(huì)先找到該函數(shù)所在的文件。假定編譯程序編譯myproj.c(其中含main())時(shí),發(fā)現(xiàn)它include了mylib.h(其中聲明了函數(shù)void test()),那么此時(shí)編譯器將按照事先設(shè)定的路徑(Include路徑列表及代碼文件所在的路徑)查找與之同名的實(shí)現(xiàn)文件(擴(kuò)展名為.cpp或.c,此例中為mylib.c),如果找到該文件,并在其中找到該函數(shù)(此例中為void test())的實(shí)現(xiàn)代碼,則繼續(xù)編譯;如果在指定目錄找不到實(shí)現(xiàn)文件,或者在該文件及后續(xù)的各include文件中未找到實(shí)現(xiàn)代碼,則返回一個(gè)編譯錯(cuò)誤.其實(shí)include的過程完全可以“看成”是一個(gè)文件拼接的過程,將聲明和實(shí)現(xiàn)分別寫在頭文件及C文件中,或者將二者同時(shí)寫在頭文件中,理論上沒有本質(zhì)的區(qū)別。以上是所謂動(dòng)態(tài)方式。對(duì)于靜態(tài)方式,基本所有的C/C++編譯器都支持一種鏈接方式被稱為Static Link,即所謂靜態(tài)鏈接。在這種方式下,我們所要做的,就是寫出包含函數(shù),類等等聲明的頭文件(a.h,b.h,...),以及他們對(duì)應(yīng)的實(shí)現(xiàn)文件(a.cpp,b.cpp,...),編譯程序會(huì)將其編譯為靜態(tài)的庫文件(a.lib,b.lib,...)。在隨后的代碼重用過程中,我們只需要提供相應(yīng)的頭文件(.h)和相應(yīng)的庫文件(.lib),就可以使用過去的代碼了。相對(duì)動(dòng)態(tài)方式而言,靜態(tài)方式的好處是實(shí)現(xiàn)代碼的隱蔽性,即C++中提倡的“接口對(duì)外,實(shí)現(xiàn)代碼不可見”。有利于庫文件的轉(zhuǎn)發(fā).c文件和.h文件的概念與聯(lián)系
如果說難題最難的部分是基本概念,可能很多人都會(huì)持反對(duì)意見,但實(shí)際上也確實(shí)如此。我高中的時(shí)候?qū)W物理,老師抓的重點(diǎn)就是概念——概念一定要搞清,于是難題也成了容易題。如果你能分析清楚一道物理難題存在著幾個(gè)物理過程,每一個(gè)過程都遵守那一條物理定律(比如動(dòng)量守恒、牛II定律、能量守恒),那么就很輕松的根據(jù)定律列出這個(gè)過程的方程,N個(gè)過程必定是N個(gè)N元方程,難題也就迎刃而解。即便是高中的物理競賽難題,最難之處也不過在于:
(1)、混淆你的概念,讓你無法分析出幾個(gè)物理過程,或某個(gè)物理過程遵循的那條物理定律;
(2)、存在高次方程,列出方程也解不出。而后者已經(jīng)是數(shù)學(xué)的范疇了,所以說,最難之處還在于掌握清晰的概念;
程序設(shè)計(jì)也是如此,如果概念很清晰,那基本上沒什么難題(會(huì)難在數(shù)學(xué)上,比如算法的選擇、時(shí)間空間與效率的取舍、穩(wěn)定與資源的平衡上)。但是,要掌握清晰的概念也沒那么容易。比如下面這個(gè)例子,看看你有沒有很清晰透徹的認(rèn)識(shí)。
//a.h
void foo();
//a.c
#include "a.h" //我的問題出來了:這句話是要,還是不要?
void foo()
{
return;
}
//main.c
#include "a.h"
int main(int argc, char *argv[])
{
foo();
return 0;
}
針對(duì)上面的代碼,請(qǐng)回答三個(gè)問題:
a.c 中的 #include "a.h" 這句話是不是多余的?
為什么經(jīng)常見 xx.c 里面 include 對(duì)應(yīng)的 xx.h?
如果 a.c 中不寫,那么編譯器是不是會(huì)自動(dòng)把 .h 文件里面的東西跟同名的 .c 文件綁定在一起?
(請(qǐng)針對(duì)上面3道題仔細(xì)考慮10分鐘,莫要著急看下面的解釋。:) 考慮的越多,下面理解的就越深。)
好了,時(shí)間到!請(qǐng)忘掉上面的3道題,以及對(duì)這三道題引發(fā)出的你的想法,然后再聽我慢慢道來。正確的概念是:從C編譯器角度看,.h和.c皆是浮云,就是改名為.txt、.doc也沒有大的分別。換句話說,就是.h和.c沒啥必然聯(lián)系。.h中一般放的是同名.c文件中定義的變量、數(shù)組、函數(shù)的聲明,需要讓.c外部使用的聲明。這個(gè)聲明有啥用?只是讓需要用這些聲明的地方方便引用。因?yàn)?#include "xx.h" 這個(gè)宏其實(shí)際意思就是把當(dāng)前這一行刪掉,把 xx.h 中的內(nèi)容原封不動(dòng)的插入在當(dāng)前行的位置。由于想寫這些函數(shù)聲明的地方非常多(每一個(gè)調(diào)用 xx.c 中函數(shù)的地方,都要在使用前聲明一下子),所以用 #include "xx.h" 這個(gè)宏就簡化了許多行代碼——讓預(yù)處理器自己替換好了。也就是說,xx.h 其實(shí)只是讓需要寫 xx.c 中函數(shù)聲明的地方調(diào)用(可以少寫幾行字),至于 include 這個(gè) .h 文件是誰,是 .h 還是 .c,還是與這個(gè) .h 同名的 .c,都沒有任何必然關(guān)系。
這樣你可能會(huì)說:???那我平時(shí)只想調(diào)用 xx.c 中的某個(gè)函數(shù),卻 include了 xx.h 文件,豈不是宏替換后出現(xiàn)了很多無用的聲明?沒錯(cuò),確實(shí)引入了很多垃圾,但是它卻省了你不少筆墨,并且整個(gè)版面也看起來清爽的多。魚與熊掌不可得兼,就是這個(gè)道理。反正多些聲明(.h一般只用來放聲明,而放不定義,參見拙著“過馬路,左右看”)也無害處,又不會(huì)影響編譯,何樂而不為呢?
翻回頭再看上面的3個(gè)問題,很好解答了吧?
答:不一定。這個(gè)例子中顯然是多余的。但是如果.c中的函數(shù)也需要調(diào)用同個(gè).c中的其它函數(shù),那么這個(gè).c往往會(huì)include同名的.h,這樣就不需要為聲明和調(diào)用順序而發(fā)愁了(C語言要求使用之前必須聲明,而include同名.h一般會(huì)放在.c的開頭)。有很多工程甚至把這種寫法約定為代碼規(guī)范,以規(guī)范出清晰的代碼來。
答:1中已經(jīng)回答過了。
答:不會(huì)。問這個(gè)問題的人絕對(duì)是概念不清,要不就是想混水摸魚。非常討厭的是中國的很多考試出的都是這種爛題,生怕別人有個(gè)清楚的概念了,絕對(duì)要把考生搞暈。
搞清楚語法和概念說易也易,說難也難。竅門有三點(diǎn):
不要暈著頭工作,要抽空多思考思考,多看看書;
看書要看好書,問人要問強(qiáng)人。爛書和爛人都會(huì)給你一個(gè)錯(cuò)誤的概念,誤導(dǎo)你;
勤能補(bǔ)拙是良訓(xùn),一分辛苦一分才;
(1)通過頭文件來調(diào)用庫功能。在很多場合,源代碼不便(或不準(zhǔn))向用戶公布,只要向用戶提供頭文件和二進(jìn)制的庫即可。用戶只需要按照頭文件中的接口聲明來調(diào)用庫功能,而不必關(guān)心接口怎么實(shí)現(xiàn)的。編譯器會(huì)從庫中提取相應(yīng)的代碼。
(2)頭文件能加強(qiáng)類型安全檢查。如果某個(gè)接口被實(shí)現(xiàn)或被使用時(shí),其方式與頭文件中的聲明不一致,編譯器就會(huì)指出錯(cuò)誤,這一簡單的規(guī)則能大大減輕程序員調(diào)試、改錯(cuò)的負(fù)擔(dān)。
頭文件用來存放函數(shù)原型。
頭文件如何來關(guān)聯(lián)源文件?
這個(gè)問題實(shí)際上是說,已知頭文件“a.h”聲明了一系列函數(shù)(僅有函數(shù)原型,沒有函數(shù)實(shí)現(xiàn)),“b.cpp”中實(shí)現(xiàn)了這些函數(shù),那么如果我想在“c.cpp”中使用“a.h”中聲明的這些在“b.cpp”中實(shí)現(xiàn)的函數(shù),通常都是在“c.cpp”中使用#include “a.h”,那么c.cpp是怎樣找到b.cpp中的實(shí)現(xiàn)呢?
其實(shí).cpp和.h文件名稱沒有任何直接關(guān)系,很多編譯器都可以接受其他擴(kuò)展名。
譚浩強(qiáng)老師的《C程序設(shè)計(jì)》一書中提到,編譯器預(yù)處理時(shí),要對(duì)#include命令進(jìn)行“文件包含處理”:將headfile.h的全部內(nèi)容復(fù)制到#include “headfile.h”處。這也正說明了,為什么很多編譯器并不care到底這個(gè)文件的后綴名是什么----因?yàn)?include預(yù)處理就是完成了一個(gè)“復(fù)制并插入代碼”的工作。
程序編譯的時(shí)候,并不會(huì)去找b.cpp文件中的函數(shù)實(shí)現(xiàn),只有在link的時(shí)候才進(jìn)行這個(gè)工作。我們在b.cpp或c.cpp中用#include “a.h”實(shí)際上是引入相關(guān)聲明,使得編譯可以通過,程序并不關(guān)心實(shí)現(xiàn)是在哪里,是怎么實(shí)現(xiàn)的。源文件編譯后成生了目標(biāo)文件(.o或.obj文件),目標(biāo)文件中,這些函數(shù)和變量就視作一個(gè)個(gè)符號(hào)。在link的時(shí)候,需要在makefile里面說明需要連接哪個(gè).o或.obj文件(在這里是b.cpp生成的.o或.obj文件),此時(shí),連接器會(huì)去這個(gè).o或.obj文件中找在b.cpp中實(shí)現(xiàn)的函數(shù),再把他們build到makefile中指定的那個(gè)可以執(zhí)行文件中。
在VC中,一幫情況下不需要自己寫makefile,只需要將需要的文件都包括在project中,VC會(huì)自動(dòng)幫你把makefile寫好。
通常,編譯器會(huì)在每個(gè).o或.obj文件中都去找一下所需要的符號(hào),而不是只在某個(gè)文件中找或者說找到一個(gè)就不找了。因此,如果在幾個(gè)不同文件中實(shí)現(xiàn)了同一個(gè)函數(shù),或者定義了同一個(gè)全局變量,鏈接的時(shí)候就會(huì)提示“redefined”.
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
文章題目:C語言中有關(guān)外部函數(shù)調(diào)用的問題-創(chuàng)新互聯(lián)
網(wǎng)頁地址:http://jinyejixie.com/article32/dijjpc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動(dòng)網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)、微信小程序、微信公眾號(hào)、網(wǎng)站建設(shè)、網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容