0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

C語言頭文件組織作用與包含原則詳解

Q4MP_gh_c472c21 ? 來源:嵌入式ARM ? 作者:clover_toeic ? 2020-11-12 17:49 ? 次閱讀

說明

本文假定讀者已具備基本的C編譯知識。

如非特殊說明,文中“源文件”指 * .c文件,“頭文件”指 *.h文件,“引用”指包含頭文件。

一、頭文件作用

C語言里,每個源文件是一個模塊,頭文件為使用該模塊的用戶提供接口。接口指一個功能模塊暴露給其他模塊用以訪問具體功能的方法。

使用源文件實(shí)現(xiàn)模塊的功能,使用頭文件暴露單元的接口。用戶只需包含相應(yīng)的頭文件就可使用該頭文件中暴露的接口。

通過頭文件包含的方法將程序中的各功能模塊聯(lián)系起來有利于模塊化程序設(shè)計:

1)通過頭文件調(diào)用庫功能。在很多場合,源代碼不便(或不準(zhǔn))向用戶公布,只要向用戶提供頭文件和二進(jìn)制庫即可。用戶只需按照頭文件中的接口聲明來調(diào)用庫功能,而不必關(guān)心接口如何實(shí)現(xiàn)。編譯器會從庫中提取相應(yīng)的代碼。

2)頭文件能加強(qiáng)類型安全檢查。若某個接口的實(shí)現(xiàn)或使用方式與頭文件中的聲明不一致,編譯器就會指出錯誤。這一簡單的規(guī)則能大大減輕程序員調(diào)試、改錯的負(fù)擔(dān)。

在預(yù)處理階段,編譯器將源文件包含的頭文件內(nèi)容復(fù)制到包含語句(#include)處。在源文件編譯時,連同被包含進(jìn)來的頭文件內(nèi)容一起編譯,生成目標(biāo)文件(.obj)。

如果所包含的頭文件非常龐大,則會嚴(yán)重降低編譯速度(使用GCC的-E選項(xiàng)可獲得并查看最終預(yù)處理完的文件)。因此,在源文件中應(yīng)僅包含必需的頭文件,且盡量不要在頭文件中包含其它頭文件。

二、 頭文件組織原則

源文件中實(shí)現(xiàn)變量、函數(shù)的定義,并指定鏈接范圍。頭文件中書寫外部需要使用的全局變量、函數(shù)聲明及數(shù)據(jù)類型和宏的定義。

建議組織頭文件內(nèi)容時遵循以下原則:

1)頭文件劃分原則:類型定義、宏定義盡量與函數(shù)聲明相分離,分別位于不同的頭文件中。內(nèi)部函數(shù)聲明頭文件與外部函數(shù)聲明頭文件相分離,內(nèi)部類型定義頭文件與外部類型定義頭文件相分離。

注意,類型和宏定義有時無法分拆為不同文件,比如結(jié)構(gòu)體內(nèi)數(shù)組成員的元素個數(shù)用常量宏表示時。因此僅分離類型宏定義與函數(shù)聲明,且分別置于*.th和*.fh文件(并非強(qiáng)制要求)。

2)頭文件的語義層次化原則:頭文件需要有語義層次。不同語義層次的類型定義不要放在一個頭文件中,不同層次的函數(shù)聲明不要放在一個頭文件中。

3)頭文件的語義相關(guān)性原則:同一頭文件中出現(xiàn)的類型定義、函數(shù)聲明應(yīng)該是語義相關(guān)的、有內(nèi)部邏輯關(guān)系的,避免將無關(guān)的定義和聲明放在一個頭文件中。

4)頭文件名應(yīng)盡量與實(shí)現(xiàn)功能的源文件相同,即module.c和module.h。但源文件不一定要包含其同名的頭文件。

5)頭文件中不應(yīng)包含本地數(shù)據(jù),以降低模塊間耦合度。

即只有源文件自己使用的類型、宏定義和變量、函數(shù)聲明,不應(yīng)出現(xiàn)在頭文件里。作用域限于單文件的私有變量和函數(shù)應(yīng)聲明為static,以防止外部調(diào)用。將私有類型置于源文件中,會提高聚合度,并減少不必要的格式外漏。

6)頭文件內(nèi)不允許定義變量和函數(shù),只能有宏、類型(typedef/struct/union/enum等)及變量和函數(shù)的聲明。特殊情況下可extern基本類型的全局變量,源文件通過包含該頭文件訪問全局變量。但頭文件內(nèi)不應(yīng)extern自定義類型(如結(jié)構(gòu)體)的全局變量,否則將迫使本不需要訪問該變量的源文件包含自定義類型所在頭文件[1]。

7)說明性頭文件不需要有對應(yīng)的源文件。此類頭文件內(nèi)大多包含大量概念性宏定義或枚舉類型定義,不包含任何其他類型定義和變量或函數(shù)聲明。此類頭文件也不應(yīng)包含任何其他頭文件。

8)使用#pragma once或header guard(亦稱include guard或macro guard)避免頭文件重復(fù)包含。#pragma once是一種非標(biāo)準(zhǔn)但已被現(xiàn)代編譯器廣泛支持的技巧,它明確告知預(yù)處理器“不要重復(fù)包含當(dāng)前頭文件”。而header guard則通過預(yù)處理命令模擬類似行為:

#ifndef_PRJ_DIR_FILE_H//必須確保headerguard宏名永不重名 #define_PRJ_DIR_FILE_H //<頭文件內(nèi)容> #endif

使用#pragma once相比header guard具有兩個優(yōu)點(diǎn):

更快。編譯器不會第二次讀取標(biāo)記#pragma once的文件,但卻會讀若干遍使用header guard 的文件(尋找#endif);

更簡單。不再需要為每個文件的header guard取名,避免宏名重名引發(fā)的“找不到聲明”問題。

缺點(diǎn)則是:

#pragma once保證物理上的同一個文件不會被包含多次,無法對頭文件中的一段代碼作#pragma once聲明。若某個頭文件具有多份拷貝(內(nèi)容相同的多個文件),pragma不能保證它們不被重復(fù)包含。當(dāng)然,這種重復(fù)包含很容易被發(fā)現(xiàn)并修正。

9) C++中要引用C函數(shù)時,函數(shù)所在頭文件內(nèi)應(yīng)包含extern "C"。

//.h文件頭部 #ifdef__cplusplus extern"C"{ #endif //<函數(shù)聲明> //.h文件尾部 #ifdef__cplusplus } #endif

被extern "C"修飾的變量和函數(shù)將按照C語言方式編譯和連接,否則編譯器將無法找到C函數(shù)定義,從而導(dǎo)致鏈接失敗。

10)頭文件內(nèi)要有面向用戶的充足注釋,從應(yīng)用角度描述接口暴露的內(nèi)容。

三、 頭文件包含原則

在實(shí)際編程中,常常因頭文件包含不當(dāng)而引發(fā)編譯時報告符號未定義的錯誤或重復(fù)定義的警告。要消除符號未定義的編譯錯誤,只需在引用符號(變量、函數(shù)、數(shù)據(jù)類型及宏等)前確保它已被聲明或定義[4]。要消除重復(fù)定義的警告,則需合理設(shè)計頭文件包含順序和層次。

建議包含頭文件時遵循以下原則:

1)源文件內(nèi)的頭文件包含順序應(yīng)從最特殊到一般,如:

#include"通用頭文件"http://內(nèi)部可能定義本模塊數(shù)據(jù)類型別名 #include"源文件同名頭文件" #include"本模塊其他頭文件" #include"自定義工具頭文件" #include"第三方頭文件" #include"平臺相關(guān)頭文件" #include"C++庫頭文件" #include"C庫頭文件"

優(yōu)點(diǎn)是每個頭文件必須include需要的關(guān)聯(lián)頭文件,否則會報錯。同時,源文件同名頭文件置于包含列表前端便于檢查該頭文件是否自完備,以及類型或函數(shù)聲明是否與標(biāo)準(zhǔn)庫沖突。

2)減少頭文件的嵌套和交叉引用,頭文件僅包含其真正需要顯式包含的頭文件。

例如,頭文件A中出現(xiàn)的類型定義在頭文件B中,則頭文件A應(yīng)包含頭文件B,除此以外的其他頭文件不允許包含。

頭文件的嵌套和交叉引用會使程序組織結(jié)構(gòu)和文件組織變得混亂,同時造成潛在的錯誤。大型工程中,原有頭文件可能會被多個其他(源或頭)文件包含,在原有頭文件中添加新的頭文件往往牽一發(fā)而動全身。若頭文件中類型定義需要其他頭文件時,可將其提出來單獨(dú)形成一個全局頭文件。

3)頭文件應(yīng)包含哪些頭文件僅取決于自身,而非包含該頭文件的源文件。

例如,編譯源文件時需要用到頭文件B,且源文件已包含頭文件A,而索性將頭文件B包含在頭文件A中,這是錯誤的做法。

4)盡量保證用戶使用此頭文件時,無需手動包含其他前提頭文件,即此頭文件內(nèi)已包含前提頭文件。

例如,面積相關(guān)操作的頭文件Area.h內(nèi)已包含關(guān)于點(diǎn)操作的頭文件Point.h,則用戶包含Area.h后無需再手動包含Point.h。這樣用戶就不必了解頭文件的內(nèi)在依賴關(guān)系。

5)頭文件應(yīng)是自完備的,即在任一源文件中包含任一頭文件而不會產(chǎn)生編譯錯誤。

6)源文件中包含的頭文件盡量不要有順序依賴。

7)盡量在源文件中包含頭文件,而非在頭文件中。且源文件僅包含所需的頭文件。

8)頭文件中若能前置聲明(亦稱前向聲明[5]),就不要包含另一頭文件。僅當(dāng)前置聲明不能滿足或過于麻煩時才使用include,如此可減少依賴性方面的問題。示例如下:

structT_MeInfoMap;//前置聲明 structT_OmciMsg;//前置聲明 typedefFUNC_STATUS(*OmciChkFunc)(structT_MeInfoMap*ptMeInfo,structT_OmciMsg*ptMsg,structT_OmciMsg*ptAckMsg); //OMCI實(shí)體信息 typedefstruct{ INT16UwMeClass;//實(shí)體類別 OMCI_ATTR_INFO*pMeAttrInfo;//實(shí)體所定義的屬性信息指針 INT8UucAttrNum;//實(shí)體所定義的屬性數(shù)目 INT16UwTotalAttrLen;//實(shí)體所有屬性所占的總字節(jié)數(shù),初始化為0,動態(tài)計算 INT8U*pszDbName;//實(shí)體存庫時的數(shù)據(jù)表名稱,建議不要超過DB_NAME_LEN(32) INT16UwMaxRecNum;//實(shí)體存庫時支持的最大記錄數(shù)目 OmciChkFuncfnCheck;//Omci校驗(yàn)函數(shù)指針 BOOLbDbCreated;//實(shí)體數(shù)據(jù)表是否已創(chuàng)建 }OMCI_ME_INFO_MAP;

如上,在OmciChkFunc函數(shù)的實(shí)現(xiàn)源文件內(nèi)包含T_MeInfoMap和T_OmciMsg所在頭文件即可。

另舉一例如下:

typedefTBL_SET_MODE(*OperTypeFunc)(INT8U*pTblEntry); typedefINT8U(*CmpRecFunc)(VOID*pvCmpData,VOID*pvRecData);//為避免頭文件交叉引用,與CompareRecFunc異名同構(gòu) //表屬性信息 typedefstruct{ INT16UwMaxEntryNum;//表屬性最大表項(xiàng)數(shù)目(實(shí)體記錄數(shù)目wMaxRecNum*wMaxEntryNum<=?MAX_RECORD_NUM) ????OperTypeFunc?fnGetOperType;??//操作類型函數(shù)指針。根據(jù)表項(xiàng)數(shù)據(jù)或外界需求(只讀表)解析當(dāng)前表項(xiàng)操作類型 ????TBL_KEY_INFO?tCmpKeyInfo;????//檢索表屬性子表記錄時的匹配關(guān)鍵字信息(TBL_KEY_INFO) ????CmpRecFunc???fnCmpAddKey;????//增加表項(xiàng)時需要檢測的關(guān)鍵字匹配函數(shù)指針 ????CmpRecFunc???fnCmpDelKey;????//刪除表項(xiàng)時需要檢測的關(guān)鍵字匹配函數(shù)指針 ????INT16U?wTblEntrySize;????????//表屬性表項(xiàng)字節(jié)數(shù),由外部動態(tài)賦值 }TBL_ATTR_INFO;

如上,CompareRecFunc函數(shù)原型由其他頭文件提供,此處為避免頭文件交叉引用定義其異名同構(gòu)原型CmpRecFunc。

在不會引起歧義的前提下,頭文件內(nèi)盡可能使用VOID指針代替非基本類型的值變量或指針,以避免再包含類型定義所在的頭文件。但這將影響代碼可讀性并降低程序執(zhí)行效率,應(yīng)權(quán)衡利弊。

9)避免包含重量級的平臺頭文件,如windows.h或d3d9.h等。若僅使用該頭文件少量函數(shù),可extern函數(shù)到源文件內(nèi)。如下:

/**************************************************************************************** 外部函數(shù)聲明(當(dāng)外部接口未提供頭文件或頭文件過于復(fù)雜時) ****************************************************************************************/ //因聲明所在頭文件引用混亂,此處僅extern函數(shù)聲明。 externINT32SDBShmCliInit(VOID);//#include"db_shm_mgr.h" externINT32ScmLockInit(VOID);//#include"common_cmapi.h"

若還使用該頭文件某些類型和宏定義,可創(chuàng)建適配性源文件。在該源文件內(nèi)包含平臺頭文件,封裝新的接口并將其聲明在同名頭文件內(nèi),其他源文件將通過適配頭文件間接訪問平臺接口。如下:

/***************************************************************************************** *文件名稱:Omci_Send_Msg.c *內(nèi)容摘要:OMCI消息轉(zhuǎn)發(fā)接口 *其它說明:該頭文件封裝SEND接口,以避免其他源文件包含支撐api和pid公共頭文件導(dǎo)致引用混亂。 *****************************************************************************************/ #include"Omci_Common.h" #include"Omci_Send_Msg.h" #include"oss_api.h" /********************************************************************************************** 函數(shù)實(shí)現(xiàn)區(qū) **********************************************************************************************/ //向自身進(jìn)程發(fā)送異步消息 INT32UOmciAsynSendSelf(INT16UwEvent,VOID*pvMsg,INT16UwMsgLen) { PIDdwSelfPid=0; SELF(&dwSelfPid); returnASEND(wEvent,pvMsg,wMsgLen,dwSelfPid); }

10)對于函數(shù)庫(包括標(biāo)準(zhǔn)庫和自定義的公共宏及接口)的頭文件,可將其加入到一個通用頭文件中。需要控制該頭文件的體積(主要是該頭文件所包含的所有頭文件內(nèi)容大小),并確保所有源文件首先包含該通用頭文件。示例如下:

#ifndef_OMCI_COMMON_H #define_OMCI_COMMON_H /******************************************************************************************* *說明: *本文件僅應(yīng)包含與具體通信協(xié)議無關(guān)的通用數(shù)據(jù)類型及宏定義。 *為簡化頭文件包含且不失可移植性,本文件內(nèi)可包含少量C庫通用頭文件。 *因本文件內(nèi)定義基本數(shù)據(jù)類型別名,故.c文件中應(yīng)將本頭文件置于包含列表頂端, *否則編譯時可能產(chǎn)生類型未定義錯誤。 *******************************************************************************************/ #include #include #include #include #include #include"Omci_Byte.h" //

注意,示例頭文件內(nèi)包含C庫文件雖能簡化包含,但卻與規(guī)則1沖突。也可另外增加包含庫文件列表的通用頭文件。

11)若不確定類型、宏定義或函數(shù)聲明所在頭文件具體路徑,可在源文件中再次定義或聲明,編譯器會以redefined警告或conflicting錯誤給出類型、宏定義或函數(shù)聲明所在頭文件路徑。

四、代碼文件組織原則

建議C語言項(xiàng)目中代碼文件組織遵循以下原則:

1)使用層次化和模塊化的軟件開發(fā)模型。每個模塊只能使用所在層和下一層模塊提供的接口。

2)每個模塊的文件(可能多個)保存在一個獨(dú)立文件夾中。

模塊文件較多時可采用子目錄的方式,物理上隔離不同層次的文件。子目錄下源文件和頭文件應(yīng)分開存放,如分別置入include和source目錄。

3)用于模塊裁減的條件編譯宏保存在一個獨(dú)立文件中,便于軟件裁減。

4)硬件相關(guān)代碼和操作系統(tǒng)相關(guān)代碼與工程代碼相對獨(dú)立保存,以便于軟件移植。

5)按相同功能或相關(guān)性組織源文件和頭文件。同一文件內(nèi)的聚合度要高,不同文件中的耦合度要低。

在對既有工程做單元測試時,耦合度低的文件布局非常便于搭建環(huán)境。

6)聲明和定義分開,使用頭文件暴露模塊需要提供給外部的類型、宏、變量和函數(shù)。盡量做到模塊對外部透明,用戶在使用模塊功能時無需了解具體的實(shí)現(xiàn)。

7)作為對外接口的頭文件一經(jīng)發(fā)布,應(yīng)保持穩(wěn)定。修改時一定要慎重。

8)文件夾和文件命名要能夠反映出模塊的功能。

9)正式版本和測試版本使用統(tǒng)一文件,使用宏控制是否產(chǎn)生測試輸出。

10)必要的注釋不可缺少。

五、 注解

「【注1】全局變量的使用原則」

1)若全局變量僅在單個源文件中訪問,則可將該變量改為該文件內(nèi)的靜態(tài)全局變量;

2)若全局變量僅由單個函數(shù)訪問,則可將該變量改為該函數(shù)內(nèi)的靜態(tài)局部變量;

3)盡量不要使用extern聲明全局變量,最好提供函數(shù)訪問這些變量。直接暴露全局變量是不安全的,外部用戶未必完全理解這些變量的含義。

4)設(shè)計和調(diào)用訪問動態(tài)全局變量、靜態(tài)全局變量、靜態(tài)局部變量的函數(shù)時,需要考慮重入問題。

「【注2】#pragma once的可移植性」

#ifndef由C/C++語言標(biāo)準(zhǔn)支持,不受編譯器任何限制;而#pragma once僅由編譯器提供保證,存在可移植性等問題。

某些gcc編譯器版本(如3.2.3)會報告“warning: #pragma once is obsolete”的警告,而其他較老版本的編譯器可能會報錯。但隨著gcc 3.4的發(fā)布,#pragma once中的一些問題(主要與符號鏈接和硬鏈接有關(guān))得以解決,#pragma once命令也標(biāo)記為“未廢棄”。

還有種寫法同時使用#pragma once和header guard編寫“可移植性”代碼,以利用編譯器可能支持的#pragma once優(yōu)化。如下:

#pragmaonce #ifndef_PRJ_DIR_FILE_H #define_PRJ_DIR_FILE_H //<頭文件內(nèi)容> #endif

該法似乎兼有兩者的優(yōu)點(diǎn)。但既然使用#ifndef就有宏名重名的風(fēng)險,也無法避免不支持#pragma once的編譯器告警或報錯,故混用兩種方法似乎不能帶來更多的好處,反倒讓不熟悉的人感到困惑。

注意,如果使用header guard,理論上可在代碼任何地方判斷當(dāng)前是否已經(jīng)包含某個頭文件。但應(yīng)避免通過該判斷來改變后續(xù)代碼的邏輯走向!

這種做法將使程序依賴于頭文件的包含順序,極不可取。若需要實(shí)現(xiàn)“若當(dāng)前包含HeaderA.h,才加入StructB結(jié)構(gòu)”,可對StructB結(jié)構(gòu)創(chuàng)建HeaderB.h頭文件,在HeaderA.h中包含HeaderB.h。

「【注3】extern "C"」

C++語言在編譯時為實(shí)現(xiàn)函數(shù)重載,會結(jié)合函數(shù)名、參數(shù)數(shù)目及類型信息而生成一個中間函數(shù)名。

例如,C++中函數(shù)void foo(int x, float y)編譯后在符號庫中生成的名字為_foo_int_float(不同編譯器可能生成不同函數(shù)名,但均采用相同機(jī)制,生成的新名字稱為”mangled name”);而該函數(shù)被C編譯器編譯后在符號庫中的名字為_foo。

C語言中不支持extern "C"聲明,在.c文件中包含extern "C"時會出現(xiàn)編譯語法錯誤。

當(dāng)然編譯器也可以為其他語言提供鏈接說明。例如:extern "FORTRAN"、extern "Ada"等。

「【注4】聲明(declaration)與定義(definition)」

全局變量或函數(shù)可(在多個編譯單元中)有多處聲明,但只允許定義一次。全局變量定義時分配空間并賦初始值(如果有);函數(shù)定義時提供函數(shù)體內(nèi)容。

聲明: externintiGlobal; externintfunc();或intfunc(); 定義: intiGlobal=0;或intiGlobal; intfunc() { return1; }

在多個源文件中共享變量或函數(shù)時,需確保定義和聲明的一致性。通常在某個相關(guān)的源文件中定義,然后在頭文件中進(jìn)行外部聲明。需要使用時包含相應(yīng)的頭文件即可。定義變量的源文件也應(yīng)包含該頭文件,以便編譯器檢查定義和聲明的一致性。

該規(guī)則可提供高度的可移植性:它與ANSI/ISO C標(biāo)準(zhǔn)一致,同時也兼顧大多數(shù)ANSI前的編譯器和鏈接器。(Unix編譯器和鏈接器常使用允許多重定義的“通用模式”,只要保證最多對一處定義進(jìn)行初始化即可。

該方式被ANSI C標(biāo)準(zhǔn)稱為一種“通用擴(kuò)展”)。某些很老的系統(tǒng)可能要求顯式初始化以區(qū)別定義和外部聲明。

通用擴(kuò)展在《深入理解計算機(jī)系統(tǒng)》中解釋為:多重定義的符號只允許最多一個強(qiáng)符號。函數(shù)和定義時已初始化的全局變量是強(qiáng)符號;未初始化的全局變量是弱符號。Unix鏈接器使用以下規(guī)則來處理多重定義的符號:

規(guī)則一:不允許有多個強(qiáng)符號。在被多個源文件包含的頭文件內(nèi)定義的全局變量會被定義多次(預(yù)處理階段會將頭文件內(nèi)容展開在源文件中),若在定義時顯式地賦值(初始化),則會違反此規(guī)則。

規(guī)則二:若存在一個強(qiáng)符號和多個弱符號,則選擇強(qiáng)符號。

規(guī)則三:若存在多個弱符號,則從這些弱符號中任選一個。

當(dāng)不同文件內(nèi)定義同名(即便類型和含義不同)的全局變量時,該變量共享同一塊內(nèi)存(地址相同)。若變量定義時均初始化,則會產(chǎn)生重定義(multiple definition)的鏈接錯誤;若某處變量定義時未初始化,則無鏈接錯誤,僅在因類型不同而大小不同時可能產(chǎn)生符號大小變化(size of symbol `XXX' changed)的編譯警告。

在最壞情況下,編譯鏈接正常,但不同文件對同名全局變量讀寫時相互影響,引發(fā)非常詭異的問題。這種風(fēng)險在使用無法接觸源碼的第三方庫時尤為突出。

因此,應(yīng)盡量避免使用全局變量。若確有必要,應(yīng)采用靜態(tài)全局變量(無強(qiáng)弱之分,且不會和其他全局符號產(chǎn)生沖突),并封裝訪問函數(shù)供外部文件調(diào)用。

「【注5】前向聲明(forward declaration)」

結(jié)構(gòu)體類型S在聲明之后定義之前是一個不完全類型(incomplete type),即已知S是一個類型,但不知道包含哪些成員。

不完全類型只能用于定義指向該類型的指針,或聲明使用該類型作為形參指針類型或返回指針類型的函數(shù)。指針類型對編譯器而言大小固定(如32位機(jī)上為四字節(jié)),不會出現(xiàn)編譯錯誤。

假設(shè)先后定義兩個結(jié)構(gòu)A和B,且兩個結(jié)構(gòu)需要互相引用。在定義A時B還沒有定義,則要引用B就需要前向聲明結(jié)構(gòu)B(struct B;)。示例如下:

typedefBOOL(*func)(constDefStruct*ptStrt); typedefstructDefStruct_t { inti; funcf; }DefStruct;

如上在DefStruct中使用回調(diào)函數(shù)func聲明,這樣交叉引用必然編譯報錯。進(jìn)行前向聲明即可:

typedefstructDefStruct_tDefStruct; typedefBOOL(*func)(constDefStruct*ptStrt); structDefStruct_t { inti; funcf; };

注意,在前向聲明和具體定義之間涉及標(biāo)識符(變量、結(jié)構(gòu)、函數(shù)等)實(shí)現(xiàn)細(xì)節(jié)的使用都是非法的。若函數(shù)被前向聲明但未被調(diào)用,則編譯和運(yùn)行正常;若前向聲明函數(shù)被調(diào)用但未被定義,則編譯正常但鏈接報錯(undefined reference)。將具體定義放在源文件中可部分避免該問題。

責(zé)任編輯:xj

原文標(biāo)題:C語言頭文件組織與包含原則

文章出處:【微信公眾號:嵌入式ARM】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • C語言
    +關(guān)注

    關(guān)注

    180

    文章

    7581

    瀏覽量

    135585
  • 頭文件
    +關(guān)注

    關(guān)注

    0

    文章

    24

    瀏覽量

    9826
  • C編譯
    +關(guān)注

    關(guān)注

    0

    文章

    4

    瀏覽量

    3355

原文標(biāo)題:C語言頭文件組織與包含原則

文章出處:【微信號:gh_c472c2199c88,微信公眾號:嵌入式微處理器】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    hex文件怎么能轉(zhuǎn)回去c語言

    將 .hex 文件直接“轉(zhuǎn)回去”為原始的C語言代碼是不可能的,因?yàn)?.hex 文件是編譯后的二進(jìn)制文件,它
    的頭像 發(fā)表于 09-02 10:46 ?860次閱讀

    hex文件如何查看原c語言代碼

    直接將 .hex 文件轉(zhuǎn)換回原始的 C 語言代碼是不可能的,因?yàn)?.hex 文件是二進(jìn)制文件,它包含
    的頭像 發(fā)表于 09-02 10:37 ?753次閱讀

    可重復(fù)頭文件的固定結(jié)構(gòu)

    年輕人,你可曾記得,在修習(xí)C語言的時候,見過這樣的字句:在創(chuàng)建頭文件的時候,一定要加入保護(hù)宏。
    的頭像 發(fā)表于 08-29 10:23 ?251次閱讀
    可重復(fù)<b class='flag-5'>頭文件</b>的固定結(jié)構(gòu)

    IDF-V4.3環(huán)境下包含了庫的頭文件會編譯報錯,為什么?

    hello_world_main.c里面包含頭文化 #include "xtensa/core-macros.h",編譯則報錯:找不到頭文件; Pss:CMakeLists.txt
    發(fā)表于 06-21 08:12

    components包含頭文件錯誤是怎么回事?

    我新建了一個工程,添加了一個BLE組件,現(xiàn)在我在BLE組件的頭文件包含了如下文件Code: Select all #include \"api/esp_gatt_common_api.h
    發(fā)表于 06-06 07:21

    請問頭文件能不能定義變量呢?

    最近在編譯一個工程的時候,突然遇到了變量重復(fù)定義的問題,根據(jù)提示打開這幾個 C 文件,并沒有發(fā)現(xiàn)定義變量的地方。后來再找一找,原來變量定義在了頭文件里面。
    的頭像 發(fā)表于 04-28 09:33 ?945次閱讀

    C語言中的頭文件

    #include 指令會指示 C 預(yù)處理器瀏覽指定的文件作為輸入。預(yù)處理器的輸出包含了已經(jīng)生成的輸出,被引用文件生成的輸出以及 #include 指令之后的文本輸出。
    發(fā)表于 02-23 14:06 ?374次閱讀

    C語言中的動態(tài)內(nèi)存管理講解

    本章將講解 C 中的動態(tài)內(nèi)存管理。C 語言為內(nèi)存的分配和管理提供了幾個函數(shù)。這些函數(shù)可以在 頭文件中找到。
    的頭像 發(fā)表于 02-23 14:03 ?347次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>中的動態(tài)內(nèi)存管理講解

    枚舉有多大?c語言枚舉end的作用是什么?

    枚舉有多大?c語言枚舉end的作用是什么? 枚舉在C語言中是一種常見的數(shù)據(jù)類型,用于定義一組相互關(guān)聯(lián)的常量或者變量。它通常用于表示一系列可能
    的頭像 發(fā)表于 01-19 14:19 ?510次閱讀

    C語言有哪些預(yù)處理操作?

    ,頭文件包含#include作用:將其他文件的內(nèi)容包含到當(dāng)前文件中。示例:#include指令用
    的頭像 發(fā)表于 12-08 15:40 ?535次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>有哪些預(yù)處理操作?

    C語言必備知識頭文件包含

    頭文件C語言中是非常重要的組成部分。
    的頭像 發(fā)表于 12-01 18:20 ?1658次閱讀

    visualc++怎么新建c語言文件

    Visual C++ 是一個集成開發(fā)環(huán)境(IDE),用于開發(fā) CC++ 程序。在 Visual C++ 中,新建一個 C
    的頭像 發(fā)表于 11-27 15:57 ?3300次閱讀

    c源程序的基本結(jié)構(gòu)是什么

    C語言源程序的基本結(jié)構(gòu)是由多個函數(shù)組成的。每個程序至少包含一個主函數(shù),也可以包含其他的函數(shù),而這些函數(shù)相互之間可以進(jìn)行相互調(diào)用,以完成特定的任務(wù)。 一、
    的頭像 發(fā)表于 11-24 10:24 ?2018次閱讀

    什么是頭文件?頭文件編寫的一般格式要求是怎樣?

    本文介紹頭文件的定義、編寫、保存及引用等方面的內(nèi)容,包括了一般的格式要求、例程等。
    的頭像 發(fā)表于 11-08 16:25 ?1549次閱讀
    什么是<b class='flag-5'>頭文件</b>?<b class='flag-5'>頭文件</b>編寫的一般格式要求是怎樣?

    請問C語言文件中的預(yù)處理操作符#和##各有什么作用?

    C語言文件中的預(yù)處理操作符#和##各有什么作用?
    發(fā)表于 11-06 08:09