色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

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

GReq_mcu168 ? 來源:CSDN ? 作者:奶蓋紅茶 ? 2021-03-25 13:57 ? 次閱讀

很多事不深入以為自己懂了,但真正用到項目上,才發現了問題。曾以為自己寫C語言已經輕車熟路了,特別是對軟件文件的工程管理上,因為心里對自己的代碼編寫風格還是有自信的。(畢竟剛畢業時老大對我最初的訓練就是編碼格式的規范化處理)

曾以為,一個.c文件對應一個.h文件,.c文件只包含它自身的.h文件就好,若.c文件中用到其他文件中的內容,則.h文件把用到的頭文件包含進來就可以了。

自己貌似一直秉承這個理念在進行代碼編寫(好可怕)。工程文件數量小時,這種理念貌似看不出問題,但隨著工程文件數量越來越多,我發現自己這種思路有了弊端:頭文件互相包含,導致編譯時自以為有些宏變量聲明了,它就能起作用,但實際測試發現這種方式編碼后,有些聲明的宏沒能起到作用。

經過領導及同事的指正,自己才明白原有的代碼編寫習慣不正確。應該秉承.c文件對應的.h文件只包含頭文件里用到的其它文件的頭文件,任何非必須的.h文件不要包含;而.c文件里面要包含用到的所有.h文件。這樣寫即使存在.c文件內頭文件重復包含也不傷大雅。

語言描述有時太抽象,還是符號舉例說明下:假如有兩個.c文件分別為A.c和B.c,自然它們都有各自的A.h和B.h文件。

原有的思路:

A.c里面只有一個#include “A.h”,而A.h所包含的就是一大堆如B.h,C.h,D.h.。..。文件,因為A.c文件里面要用到B.h,C.h,D.h里面的內容。如圖一所示。

新思路:

A.h里面只包含A.h所寫內容要用到的.h文件,很多時候A.h里面無需任何.h文件。而在A.c文件內就要寫成 #include “B.h” #include “C.h” #include “D.h”。而且兩個文件的.c文件在頭文件包含上可以互相包含。如圖二所示。

0fd827b6-8ced-11eb-8b86-12bb97331649.png

105feb24-8ced-11eb-8b86-12bb97331649.jpg

項目中遇到的這個頭文件包含問題導致我重新搜索資料進行該問題的深入了解,故下文是通過網絡資源的搜查及加上自己對它的理解,進行了相關內容的整理,希望對感興趣的小伙伴有所幫助。

背景

對于C語言來說,頭文件的設計體現了大部分的系統設計。不合理的頭文件布局是編譯時間過長的根因,不合理的頭文件實際上不合理的設計。

依賴

特指編譯依賴。若x.h包含了y.h,則稱作x依賴y。依賴關系會進行傳導,如x.h包含y.h,而y.h又包含了z.h,則x通過y依賴了z。依賴將導致編譯時間的上升。

雖然依賴是不可避免的,也是必須的,但是不良的設計會導致整個系統的依賴關系無比復雜,使得任意一個文件的修改都要重新編譯整個系統,導致編譯時間巨幅上升。

在一個設計良好的系統中, 修改一個文件,只需要重新編譯數個,甚至是一個文件。

產品曾經做過一個實驗,把所有函數的實現通過工具注釋掉,其編譯時間只減少了不到10%,究其原因,在于A包含B, B包含C, C包含D,最終幾乎每一個源文件都包含了項目組所有的頭文件,從而導致絕大部分編譯時間都花在解析頭文件上。

某產品更有一個“優秀實踐”,用于將.c文件通過工具合并成一個比較大的.c文件,從而大幅度提高編譯效率。

其根本原因還是在于通過合并.c文件減少了頭文件解析次數。但是,這樣的“優秀實踐”是對合理劃分.c文件的一種破壞。

大部分產品修改一處代碼,都得需要編譯整個工程,對于TDD之類的實踐,要求對于模塊級別的編譯時間控制在秒級,即使使用分布式編譯也難以實現,最終仍然需要合理的劃分頭文件、以及頭文件之間的包含關系, 從根本上降低編譯時間。

《google C++ Style Guide》 1.2 頭文件依賴 章節也給出了類似的闡述:若包含了頭文件aa.h,則就引入了新的依賴:一旦aa.h被修改,任何直接和間接包含aa.h代碼都會被重新編譯。如果aa.h又包含了其他頭文件如bb.h,那么bb.h的任何改變都將導致所有包含了aa.h的代碼被重新編譯。

在敏捷開發方式下,代碼會被頻繁構建,漫長的編譯時間將極大的阻礙頻繁構建。因此,我們傾向于減少包含頭文件,尤其是在頭文件中包含頭文件,以控制改動代碼后的編譯時間。

合理的頭文件劃分體現了系統設計的思想,但是從編程規范的角度看,仍然有一些通用的方法,用來合理規劃頭文件。本章節介紹的一些方法,對于合理規劃頭文件會有一定的幫助。

原則1:頭文件中適合放置接口的聲明,不適合放置實現。

說明:頭文件是模塊( Module)或單元( Unit)的對外接口。頭文件中應放置對外部的聲明,如對外提供的函數聲明、宏定義、類型定義等。

內部使用的函數(相當于類的私有方法)聲明不應放在頭文件中。

內部使用的宏、枚舉、結構定義不應放入頭文件中。

變量定義不應放在頭文件中,應放在.c文件中。

變量的聲明盡量不要放在頭文件中,亦即盡量不要使用全局變量作為接口 。變量是模塊或單元的內部實現細節,不應通過在頭文件中聲明的方式直接暴露給外部,應通過函數接口的方式進行對外暴露。

延伸閱讀材料:《 C語言接口與實現》

原則2:頭文件應當職責單一。

說明:頭文件過于復雜,依賴過于復雜是導致編譯時間過長的主要原因。很多現有代碼中頭文件過大,職責過多, 再加上循環依賴的問題,可能導致為了在.c中使用一個宏,而包含十幾個頭文件。

某個頭文件不但定義了基本數據類型WORD,還包含了stdio.h syslib.h等等不常用的頭文件。

如果工程中有10000個源文件,而其中100個源文件使用了stdio.h的printf,由于上述頭文件的職責過于龐大,而WORD又是每一個文件必須包含的,從而導致stdio.h/syslib.h等可能被不必要的展開了9900次,大大增加了工程的編譯時間。

原則3:頭文件應向穩定的方向包含。

說明:頭文件的包含關系是一種依賴,一般來說,應當讓不穩定的模塊依賴穩定的模塊,從而當不穩定的模塊發生變化時,不會影響(編譯)穩定的模塊。

就我們的產品來說,依賴的方向應該是:產品依賴于平臺,平臺依賴于標準庫。某產品線平臺的代碼中已經包含了產品的頭文件,導致平臺無法單獨編譯、發布和測試, 是一個非常糟糕的反例。

除了不穩定的模塊依賴于穩定的模塊外,更好的方式是兩個模塊共同依賴于接口,這樣任何一個模塊的內部實現更改都不需要重新編譯另外一個模塊。在這里,我們假設接口本身是最穩定的。

延伸閱讀材料:編者推薦開發人員使用“依賴倒置”原則,即由使用者制定接口,服務提供者實現接口,更具體的描述可以參見《 敏捷軟件開發:原則、模式與實踐》 ( Robert C.Martin 著 鄧輝 譯 清華大學出版社2003年9月) 的第二部分“敏捷設計”章節。

規則1:每一個.c文件應有一個同名.h文件,用于聲明需要對外公開的接口。

說明:如果一個.c文件不需要對外公布任何接口,則其就不應當存在,除非它是程序的入口,如main函數所在的文件。

現有某些產品中,習慣一個.c文件對應兩個頭文件,一個用于存放對外公開的接口,一個用于存放內部需要用到的定義、聲明等,以控制.c文件的代碼行數。編者不提倡這種風格。

這種風格的根源在于源文件過大,應首先考慮拆分.c文件,使之不至于太大。另外,一旦把私有定義、聲明放到獨立的頭文件中,就無法從技術上避免別人include之,難以保證這些定義最后真的只是私有的。

本規則反過來并不一定成立。有些特別簡單的頭文件,如命令ID定義頭文件,不需要有對應的.c存在[a1] 。

示例:對于如下場景,如在一個.c中存在函數調用關系:

void foo() { bar(); } void bar() { Do something; }

必須在foo之前聲明bar,否則會導致編譯錯誤。

這一類的函數聲明,應當在.c的頭部聲明,并聲明為static的,如下:

static void bar(); void foo() { bar(); } void bar() { Do something; }

規則2:禁止頭文件循環依賴。

說明:頭文件循環依賴,指a.h包含b.h, b.h包含c.h, c.h包含a.h之類導致任何一個頭文件修改,都導致所有包含了a.h/b.h/c.h的代碼全部重新編譯一遍。

而如果是單向依賴,如a.h包含b.h, b.h包含c.h,而c.h不包含任何頭文件,則修改a.h不會導致包含了b.h/c.h的源代碼重新編譯。

規則3:.c/.h文件禁止包含用不到的頭文件。

說明:很多系統中頭文件包含關系復雜,開發人員為了省事起見,可能不會去一一鉆研,直接包含一切想到的頭文件,甚至有些產品干脆發布了一個god.h,其中包含了所有頭文件,然后發布給各個項目組使用,這種只圖一時省事的做法,導致整個系統的編譯時間進一步惡化,并對后來人的維護造成了巨大的麻煩。

規則4:頭文件應當自包含。

說明:簡單的說,自包含就是任意一個頭文件均可獨立編譯。如果一個頭文件包含某個頭文件,還要包含另外一個頭文件才能工作的話,就會增加交流障礙,給這個頭文件的用戶增添不必要的負擔[a2] 。

示例:如果a.h不是自包含的,需要包含b.h才能編譯,會帶來的危害:每個使用a.h頭文件的.c文件,為了讓引入的a.h的內容編譯通過,都要包含額外的頭文件b.h。額外的頭文件b.h必須在a.h之前進行包含,這在包含順序上產生了依賴。

注意:該規則需要與“ .c/.h文件禁止包含用不到的頭文件”規則一起使用,不能為了讓a.h自包含,而在a.h中包含不必要的頭文件。a.h要剛剛可以自包含,不能在a.h中多包含任何滿足自包含之外的其他頭文件。

規則5:總是編寫內部#include保護符( #define 保護)。

說明:多次包含一個頭文件可以通過認真的設計來避免。如果不能做到這一點,就需要采取阻止頭文件內容被包含多于一次的機制。

通常的手段是為每個文件配置一個宏,當頭文件第一次被包含時就定義這個宏,并在頭文件被再次包含時使用它以排除文件內容。

所有頭文件都應當使用#define 防止頭文件被多重包含,命名格式為FILENAME_H,為了保證唯一性,更好的命名是PROJECTNAME_PATH_FILENAME_H。

注:沒有在宏最前面加上““,即使用FILENAME_H代替_FILENAME_H,是因為一般以”“和”“開頭的標識符為系統保留或者標準庫使用,在有些靜態檢查工具中,若全局可見的標識符以””開頭會給出告警。

定義包含保護符時,應該遵守如下規則:

1)保護符使用唯一名稱;

2)不要在受保護部分的前后放置代碼或者注釋。

示例:假定VOS工程的timer模塊的timer.h,其目錄為VOS/include/timer/timer.h,應按如下方式保護:

#ifndef VOS_INCLUDE_TIMER_TIMER_H #define VOS_INCLUDE_TIMER_TIMER_H 。.. #endif

也可以使用如下簡單方式保護:

#ifndef TIMER_H #define TIMER_H 。. #endif

例外情況:頭文件的版權聲明部分以及頭文件的整體注釋部分(如闡述此頭文件的開發背景、使用注意事項等)可以放在保護符(#ifndef XX_H)前面。

規則6:禁止在頭文件中定義變量。

說明:在頭文件中定義變量,將會由于頭文件被其他.c文件包含而導致變量重復定義。

規則7:只能通過包含頭文件的方式使用其他.c提供的接口,禁止在.c中通過extern的方式使用外部函數接口、變量[a3] 。

說明:若a.c使用了b.c定義的foo()函數,則應當在b.h中聲明extern int foo(int input);并在a.c中通過#include 來使用foo。

禁止通過在a.c中直接寫extern int foo(int input);來使用foo,后面這種寫法容易在foo改變時可能導致聲明和定義不一致[a4] 。

規則8:禁止在extern “C”中包含頭文件。

說明:在extern “C”中包含頭文件, 會導致extern “C”嵌套, Visual Studio對extern “C”嵌套層次有限制,嵌套層次太多會編譯錯誤。

在extern “C”中包含頭文件,可能會導致被包含頭文件的原有意圖遭到破壞。例如,存在a.h和b.h兩個頭文件:

使用C++預處理器展開b.h,將會得到

extern “C” { void foo(int); void b(); }

按照a.h作者的本意,函數foo是一個C++自由函數,其鏈接規范為”C++”。但在b.h中,由于#include “a.h”被放到了extern “C” { }的內部,函數foo的鏈接規范被不正確地更改了。

示例:錯誤的使用方式:

extern “C” { #include “xxx.h” 。.. }

正確的使用方式:

#include “xxx.h” extern “C” { 。.. }

建議1:一個模塊通常包含多個.c文件,建議放在同一個目錄下,目錄名即為模塊名。為方便外部使用者,建議每一個模塊提供一個.h,文件名為目錄名。

說明:需要注意的是,這個.h并不是簡單的包含所有內部的.h,它是為了模塊使用者的方便,對外整體提供的模塊接口。

以Google test(簡稱GTest)為例, GTest作為一個整體對外提供C++單元測試框架,其1.5版本的gtest工程下有6個源文件和12個頭文件。

但是它對外只提供一個gtest.h,只要包含gtest.h即可使用GTest提供的所有對外提供的功能,使用者不必關系GTest內部各個文件的關系,即使以后GTest的內部實現改變了,比如把一個源文件c拆成兩個源文件,使用者也不必關心,甚至如果對外功能不變,連重新編譯都不需要。

對于有些模塊,其內部功能相對松散,可能并不一定需要提供這個.h,而是直接提供各個子模塊或者.c的頭文件。

比如產品普遍使用的VOS,作為一個大模塊,其內部有很多子模塊,他們之間的關系相對比較松散,就不適合提供一個vos.h。而VOS的子模塊,如Memory(僅作舉例說明,與實際情況可能有所出入),其內部實現高度內聚,雖然其內部實現可能有多個.c和.h,但是對外只需要提供一個Memory.h聲明接口。

建議2:如果一個模塊包含多個子模塊,則建議每一個子模塊提供一個對外的.h,文件名為子模塊名。

說明:降低接口使用者的編寫難度。

建議3:頭文件不要使用非習慣用法的擴展名,如.inc。

說明:目前很多產品中使用了.inc作為頭文件擴展名,這不符合c語言的習慣用法。在使用.inc作為頭文件擴展名的產品,習慣上用于標識此頭文件為私有頭文件。

但是從產品的實際代碼來看,這一條并沒有被遵守,一個.inc文件被多個.c包含比比皆是。本規范不提倡將私有定義單獨放在頭文件中,具體見 規則1.1。

除此之外,使用.inc還導致source insight、 Visual stduio等IDE工具無法識別其為頭文件,導致很多功能不可用,如“跳轉到變量定義處”。

雖然可以通過配置,強迫IDE識別.inc為頭文件,但是有些軟件無法配置,如Visual Assist只能識別.h而無法通過配置識別.inc。

建議4:同一產品統一包含頭文件排列方式。

說明:常見的包含頭文件排列方式:功能塊排序、文件名升序、穩定度排序。

以穩定度排序,建議將不穩定的頭文件放在前面,如把產品的頭文件放在平臺的頭文件前面,如下:

相對來說, product.h修改的較為頻繁,如果有錯誤,不必編譯platform.h就可以發現product.h的錯誤,可以部分減少編譯時間。

[a1] 例如一些屏驅動的地址文件,一些協議的格式定義文件。只存在.c或者.h即可,不一定兩者都要有。

[a2] 我對自包含沒有太理解,只是明白在.h文件里盡量不包含沒有必要的頭文件,某些情況下不得已才進行包含其它頭文件的操作。

[a3] 這種做法我寫代碼常用,但后面應該盡量避免,而是通過調用頭文件的方式來使用該函數。

[a4] 對,我就遇到過。因為隨著工程量的增大,后面某個細節調整了foo函數,但其它extern調用它的地方沒有及時改正,而KEIL編譯器又沒有報錯,導致bug出現,而且不易查找。

原文標題:C語言的頭文件包含,竟然有那么多講究!

文章出處:【微信公眾號:玩轉單片機】歡迎添加關注!文章轉載請注明出處。

責任編輯:haq

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • C語言
    +關注

    關注

    180

    文章

    7608

    瀏覽量

    137122
  • 編程
    +關注

    關注

    88

    文章

    3628

    瀏覽量

    93811

原文標題:C語言的頭文件包含,竟然有那么多講究!

文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    EE-62:在C語言中訪問短字內存

    電子發燒友網站提供《EE-62:在C語言中訪問短字內存.pdf》資料免費下載
    發表于 01-07 14:02 ?0次下載
    EE-62:在<b class='flag-5'>C</b><b class='flag-5'>語言中</b>訪問短字內存

    EE-128:C語言中的DSP:從C調用匯編類成員函數

    電子發燒友網站提供《EE-128:C語言中的DSP:從C調用匯編類成員函數.pdf》資料免費下載
    發表于 01-07 13:48 ?0次下載
    EE-128:<b class='flag-5'>C</b><b class='flag-5'>語言中</b>的DSP:從<b class='flag-5'>C</b>調用匯編類成員函數

    C語言中頭文件能不能重復包含

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

    編譯器怎么處理同名頭文件

    C語言中的include很簡單,但不是你想象中的簡單。
    的頭像 發表于 11-05 16:51 ?366次閱讀
    編譯器怎么處理同名<b class='flag-5'>頭文件</b>

    C語言中的socket編程基礎

    Socket編程簡介 Socket是一種通信機制,允許程序之間進行通信。在C語言中,socket編程是網絡編程的基礎。通過使用socket,程序可以發送和接收數據,實現不同計算機之間的通信
    的頭像 發表于 11-01 16:51 ?377次閱讀

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

    直接將 .hex 文件轉換回原始的 C 語言代碼是不可能的,因為 .hex 文件是二進制文件,它包含
    的頭像 發表于 09-02 10:37 ?2511次閱讀

    可重復頭文件的固定結構

    年輕人,你可曾記得,在修習C語言的時候,見過這樣的字句:在創建頭文件的時候,一定要加入保護宏。
    的頭像 發表于 08-29 10:23 ?362次閱讀
    可重復<b class='flag-5'>頭文件</b>的固定結構

    c語言中從左到右結合怎么看

    C語言中,操作符的結合性(Associativity)是指當操作符在表達式中連續出現時,它們如何與操作數結合的順序。對于大多數二元操作符(即需要兩個操作數的操作符),C語言遵循兩種基
    的頭像 發表于 08-20 11:42 ?969次閱讀

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

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

    請問Keil uVision5STM32L151C8T6怎么引用頭文件?

    請問Keil uVision5STM32L151C8T6怎么引用頭文件呢?
    發表于 04-07 07:23

    介紹C語言中錯誤處理和異常處理的一些常用的方法和策略

    C語言是一種低級的、靜態的、結構化的編程語言,它沒有提供像C++或Java等高級語言中的異常處理機制,例如try-catch-finally
    的頭像 發表于 02-28 14:25 ?642次閱讀

    C語言中頭文件

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

    C語言中的動態內存管理講解

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

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

    可以是整數或字符,它們被稱為枚舉常量。枚舉常量可以通過枚舉成員來引用。 枚舉的大小是取決于它所表示的范圍的大小。在C語言中,枚舉沒有固定的大小,而是根據其取值范圍來確定的。如果枚舉類型只包含一個枚舉常量,則其大小將與該
    的頭像 發表于 01-19 14:19 ?630次閱讀

    如何解決C語言中的“訪問權限沖突”異常?C語言引發異常原因分析

    如何解決C語言中的“訪問權限沖突”異常?C語言引發異常原因分析? 在C語言中,訪問權限沖突異常通
    的頭像 發表于 01-12 16:03 ?5915次閱讀
    主站蜘蛛池模板: 国产九色在线| 亚洲精品成人a在线观看| 啊灬啊灬啊灬快灬深高潮啦| 乡村教师电影版| 欧美大香线蕉线伊人久久| 国产香蕉视频在线观看| WWW国产亚洲精品久久| 亚洲中文字幕永久在线| 色欲久久精品AV无码| 蜜芽tv在线观看免费网站| 国产中文字幕在线| 多人乱肉高hnp| 99视频精品免视3| 伊人草久久| 亚洲AV无码A片在线观看蜜桃| 秋霞鲁丝片Av无码| 美女gif趴跪式动态图| 黄桃AV无码免费一区二区三区| 国产成人精品视频播放| AV无码国产精品午夜A片麻豆| 一区三区三区不卡| 亚洲成 人a影院青久在线观看| 日韩欧美群交P内射捆绑| 欧美14videosex性欧美成人| 久久频这里精品99香蕉久网址| 国产亚洲一区在线| 国产精品VIDEOS麻豆TUBE| 调教日本美女| 成 人 免费 黄 色 网站无毒下载| 99er4久久视频精品首页| 精品一品国产午夜福利视频| 国产二级一片内射视频播放| 成年无码av片| xxxx18动漫| metart中国撒尿人体欣赏| 99精品国产高清自在线看超| 中文字幕永久在线| 中文无码有码亚洲 欧美| 一二三四在线观看高清电视剧 | 2022年国产精品久久久久| 亚洲一区精品伊人久久伊人|