一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

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

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

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

C語言的頭文件組織與包含原則

Wildesbeast ? 來源:21IC ? 作者:21IC ? 2020-11-14 11:31 ? 次閱讀

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 //必須確保header guard宏名永不重名#define _PRJ_DIR_FILE_H//《頭文件內(nèi)容》#endif

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

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

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

缺點(diǎn)則是:

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

9) C++中要引用C函數(shù)時(shí),函數(shù)所在頭文件內(nèi)應(yīng)包含extern “C”。//.h文件頭部#ifdef __cplusplusextern “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ā)編譯時(shí)報(bào)告符號未定義的錯(cuò)誤或重復(fù)定義的警告。要消除符號未定義的編譯錯(cuò)誤,只需在引用符號(變量、函數(shù)、數(shù)據(jù)類型及宏等)前確保它已被聲明或定義[4]。要消除重復(fù)定義的警告,則需合理設(shè)計(jì)頭文件包含順序和層次。

建議包含頭文件時(shí)遵循以下原則:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

struct T_MeInfoMap; //前置聲明struct T_OmciMsg; //前置聲明typedef FUNC_STATUS (*OmciChkFunc)(struct T_MeInfoMap *ptMeInfo, struct T_OmciMsg *ptMsg, struct T_OmciMsg *ptAckMsg);//OMCI實(shí)體信息typedef struct{ INT16U wMeClass; //實(shí)體類別 OMCI_ATTR_INFO *pMeAttrInfo; //實(shí)體所定義的屬性信息指針 INT8U ucAttrNum; //實(shí)體所定義的屬性數(shù)目 INT16U wTotalAttrLen; //實(shí)體所有屬性所占的總字節(jié)數(shù),初始化為0,動(dòng)態(tài)計(jì)算 INT8U *pszDbName; //實(shí)體存庫時(shí)的數(shù)據(jù)表名稱,建議不要超過DB_NAME_LEN(32) INT16U wMaxRecNum; //實(shí)體存庫時(shí)支持的最大記錄數(shù)目 OmciChkFunc fnCheck; //Omci校驗(yàn)函數(shù)指針 BOOL bDbCreated; //實(shí)體數(shù)據(jù)表是否已創(chuàng)建}OMCI_ME_INFO_MAP;

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

另舉一例如下:

typedef TBL_SET_MODE (*OperTypeFunc)(INT8U *pTblEntry);typedef INT8U (*CmpRecFunc)(VOID *pvCmpData, VOID *pvRecData); //為避免頭文件交叉引用,與CompareRecFunc異名同構(gòu)//表屬性信息typedef struct{ INT16U wMaxEntryNum; //表屬性最大表項(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; //檢索表屬性子表記錄時(shí)的匹配關(guān)鍵字信息(TBL_KEY_INFO) CmpRecFunc fnCmpAddKey; //增加表項(xiàng)時(shí)需要檢測的關(guān)鍵字匹配函數(shù)指針 CmpRecFunc fnCmpDelKey; //刪除表項(xiàng)時(shí)需要檢測的關(guān)鍵字匹配函數(shù)指針 INT16U wTblEntrySize; //表屬性表項(xiàng)字節(jié)數(shù),由外部動(dòng)態(tài)賦值}TBL_ATTR_INFO;

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

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

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

/**************************************************************************************** 外部函數(shù)聲明 (當(dāng)外部接口未提供頭文件或頭文件過于復(fù)雜時(shí)) ****************************************************************************************///因聲明所在頭文件引用混亂,此處僅extern函數(shù)聲明。extern INT32S DBShmCliInit(VOID); //#include “db_shm_mgr.h”extern INT32S cmLockInit(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ā)送異步消息INT32U OmciAsynSendSelf(INT16U wEvent, VOID *pvMsg, INT16U wMsgLen){ PID dwSelfPid = 0; SELF(&dwSelfPid); return ASEND(wEvent, pvMsg, wMsgLen, dwSelfPid);}

10)對于函數(shù)庫(包括標(biāo)準(zhǔn)庫和自定義的公共宏及接口)的頭文件,可將其加入到一個(gè)通用頭文件中。需要控制該頭文件的體積(主要是該頭文件所包含的所有頭文件內(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)將本頭文件置于包含列表頂端,* 否則編譯時(shí)可能產(chǎn)生類型未定義錯(cuò)誤。*******************************************************************************************/#include #include #include #include #include #include “Omci_Byte.h”//

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#pragma once#ifndef _PRJ_DIR_FILE_H#define _PRJ_DIR_FILE_H//《頭文件內(nèi)容》#endif

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

注意,如果使用header guard,理論上可在代碼任何地方判斷當(dāng)前是否已經(jīng)包含某個(gè)頭文件。但應(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í)為實(shí)現(xiàn)函數(shù)重載,會(huì)結(jié)合函數(shù)名、參數(shù)數(shù)目及類型信息而生成一個(gè)中間函數(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”時(shí)會(huì)出現(xiàn)編譯語法錯(cuò)誤。

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

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

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

聲明:extern int iGlobal;extern int func(); 或int func();定義:int iGlobal = 0; 或int iGlobal;int func (){ return 1;}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

typedef BOOL (*func)(const DefStruct *ptStrt); typedef struct DefStruct_t{ int i; func f;}DefStruct;

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

typedef struct DefStruct_t DefStruct;typedef BOOL (*func)(const DefStruct *ptStrt);struct DefStruct_t{ int i; func f;};

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

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

    關(guān)注

    180

    文章

    7628

    瀏覽量

    139755
  • 函數(shù)
    +關(guān)注

    關(guān)注

    3

    文章

    4365

    瀏覽量

    63872
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1652

    瀏覽量

    49740
收藏 人收藏

    評論

    相關(guān)推薦

    【Makefile】C文件包含頭文件修改,但不重新編譯?

    【Linux + Makefile】Makefile的高階用法:解決C文件包含頭文件修改了,但C文件
    的頭像 發(fā)表于 09-08 08:53 ?5913次閱讀
    【Makefile】<b class='flag-5'>C</b><b class='flag-5'>文件</b><b class='flag-5'>包含</b>的<b class='flag-5'>頭文件</b>修改,但不重新編譯?

    你應(yīng)該搞懂的 C 語言頭文件路徑問題

    前段時(shí)間在寫 Linux 專欄的過程中,忽然想到一個(gè)問題 :C語言頭文件路徑,因?yàn)樵贕CC環(huán)境下面,可以很直觀的了解到程序?qū)?b class='flag-5'>頭文件的查找位置,但是對于使用集成開發(fā)環(huán)境 KEIL 或者
    的頭像 發(fā)表于 06-14 11:41 ?5793次閱讀
    你應(yīng)該搞懂的 <b class='flag-5'>C</b> <b class='flag-5'>語言</b><b class='flag-5'>頭文件</b>路徑問題

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

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

    IAR的workspace文件組織

    IAR的workspace文件組織歡迎研究ZigBee的朋友和我交流。。。
    發(fā)表于 08-12 21:29

    VxWorks BSP框架源代碼包含頭文件和驅(qū)動(dòng)

    VxWorks BSP框架源代碼包含頭文件和驅(qū)動(dòng)
    發(fā)表于 03-26 15:58 ?114次下載

    F28035實(shí)現(xiàn)雙電機(jī)控制的程序,包含C文件、關(guān)鍵頭文件和CMD

    F28035實(shí)現(xiàn)雙電機(jī)控制的程序,包含C文件、關(guān)鍵頭文件和CMD文件
    發(fā)表于 11-02 10:57 ?40次下載

    單片機(jī)C語言頭文件

    介紹有關(guān)于單片機(jī)C51編程時(shí)候有關(guān)頭文件的相關(guān)知識,自己整理,歡迎指正!
    發(fā)表于 12-10 15:56 ?12次下載

    如何在C++代碼中使用C頭文件

    如何在C++代碼中使用C頭文件。從C++調(diào)用C頭文件之前,C
    發(fā)表于 10-19 09:24 ?3次下載

    C語言中程序員編寫的頭文件和編譯器自帶的頭文件

    #include 指令會(huì)指示 C 預(yù)處理器瀏覽指定的文件作為輸入。預(yù)處理器的輸出包含了已經(jīng)生成的輸出,被引用文件生成的輸出以及 #include 指令之后的文本輸出。例如,如果您
    的頭像 發(fā)表于 11-12 14:55 ?8028次閱讀

    C語言頭文件是做什么的

    c語言程序代碼文件擴(kuò)展名只能是.c或者.h,換句話說,c語言程序代碼只能在擴(kuò)展名為.
    的頭像 發(fā)表于 02-13 15:29 ?9636次閱讀

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

    說明 本文假定讀者已具備基本的C編譯知識。 如非特殊說明,文中源文件指 * .c文件,頭文件指 *.h
    的頭像 發(fā)表于 11-12 17:49 ?3073次閱讀

    C語言中頭文件包含里的那些事

    就是編碼格式的規(guī)范化處理) 曾以為,一個(gè).c文件對應(yīng)一個(gè).h文件,.c文件包含它自身的.h
    的頭像 發(fā)表于 03-25 13:57 ?2537次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言中頭文件</b><b class='flag-5'>包含</b>里的那些事

    C 語言頭文件路徑位置問題

    前言 前段時(shí)間在寫 Linux 專欄的過程中,忽然想到一個(gè)問題 :C語言頭文件路徑,因?yàn)樵贕CC環(huán)境下面,可以很直觀的了解到程序?qū)?b class='flag-5'>頭文件的查找位置,但是對于使用集成開發(fā)環(huán)境 KEIL
    的頭像 發(fā)表于 06-22 10:05 ?6700次閱讀
    <b class='flag-5'>C</b> <b class='flag-5'>語言</b>的<b class='flag-5'>頭文件</b>路徑位置問題

    C語言中的頭文件

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

    C語言中的頭文件能不能重復(fù)包含

    C語言中的頭文件能不能重復(fù)包含? 比如代碼寫成這樣,stdio.h 連續(xù)包含了兩次。 #include #include int main(
    的頭像 發(fā)表于 11-26 17:19 ?436次閱讀