精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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

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

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

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

TopSemic嵌入式 ? 來源:裸機思維 ? 2024-08-29 10:23 ? 次閱讀

以下文章來源于裸機思維,作者GorgonMeducer 傻孩子

【說在前面的話】

有人的地方就有江湖。我想應該沒人愿意自廢武功吧?

年輕人,你可曾記得,在修習C語言的時候,見過這樣的字句:在創(chuàng)建頭文件的時候,一定要加入保護宏。例如:

/*這是頭文件my_header.h的開頭*/
#ifndef__MY_HEADER_H__
#define __MY_HEADER_H__


/*頭文件的實體內(nèi)容 */


#endif/*endof__MY_HEANDER_H__
有好問者打破砂鍋問到底,定有那先來者苦口婆心:這是防止頭文件被有意無意間重復包含的時候出現(xiàn)內(nèi)容重復定義的問題

此話不虛、亦非假話。

但……它從一開始就隱藏了C語言預處理的一項普普通通的技法,并將其活生生逼成了所謂的武林絕學——并非因為它有怎樣的禁忌,僅僅只是因為自廢武功的人太多——幾近滅絕啊。

【未曾設想的道路】

一般情況下,我們創(chuàng)建的頭文件都可以被歸入“不可重入”的大類,顧名思義,就是如果這個頭文件被同一個 C 源文件直接或間接的包含(include)了多次,那么就會出現(xiàn)“內(nèi)容重復定義”的問題——正因為不可重入,才需要加入保護宏來確保:

頭文件中的內(nèi)容僅在第一次被包含時生效

隨后再次包含該頭文件時,內(nèi)容將被跳過

與“不可重入”的頭文件相對,還有另外一個大類被稱為“可重入的頭文件”——顧名思義,這類頭文件不僅允許出現(xiàn)重復包含,而且每一次包含都會發(fā)揮(一樣或者不一樣的)功能。

其實,在本系列之前的文章《【為宏正名】什么?我忘了去上“數(shù)學必修課”!》就已經(jīng)介紹過一個可重入頭文件mf_u8_dec2str.h 了,它的作用是在每次調(diào)用時“將用戶給定的表達式計算出結(jié)果并轉(zhuǎn)化為十進制字符串”(當然這里的數(shù)值必須小于256),例如:

//! 一個用于表示序號的宏,初值是0
#defineMY_INDEX0

每次使用下面的預編譯代碼,我們就可以實現(xiàn)將 MY_INDEX的值加一的效果:

//!MFUNC_IN_U8_DEC_VALUE=MY_INDEX+ 1;給腳本提供輸入
#defineMFUNC_IN_U8_DEC_VALUE    (MY_INDEX+1)


//!讓預編譯器執(zhí)行腳本
#include "mf_u8_dec2str.h"


#undef MY_INDEX
//!MY_INDEX=MFUNC_OUT_DEC_STR;獲得腳本輸出
#define MY_INDEX    MFUNC_OUT_DEC_STR

作為一個可重入頭文件,你調(diào)用他多少次都可以——每次都可以發(fā)揮應有的作用。對于這個頭文件的用途和原理感到好奇的小伙伴,不妨單擊這里,重新閱讀一下這篇文章。需要注意的是,最新的源代碼已經(jīng)進行了更新,文章中提及的只是原理,具體實現(xiàn)以最新的源代碼為準

https://github.com/GorgonMeducer/Generic_MCU_Software_Infrastructure/blob/master/sources/gmsi/utilities/preprocessor/mf_u8_dec2str.h

【重復包含頭文件的意義何在】

我們什么時候回會用到“可重入的頭文件”呢?或者換個問法:“可重入頭文件究竟有何作用”? 從發(fā)揮作用的方式來說,“可重入頭文件”可以被主要分為三大類:

重復提供簡單的預處理服務(比如前面提到過的mf_u8_dec2str.h

通過遞歸調(diào)用的方式來進行代碼生成(比如在編譯時刻給一個數(shù)組填充0~255的初始值);

為同樣的宏模板提供不同的解釋

第一個大類,我們已經(jīng)在文章【為宏正名】什么?我忘了去上“數(shù)學必修課”!》中詳細介紹過,這里就不再贅述。而借助 mf_u8_dec2str.h 的幫助,我們也可以很輕松的實現(xiàn)第二類功能。

假設,我們要定義一系列數(shù)據(jù),以固定間隔向其中填充指定數(shù)量的初始值,比如:


//2位Alpha 對應 8bit Alpha的備查表
constuint8_tc_chAlphaA4Table[4]={
    0,  85, 170, 255
};


//4位Alpha對應8bitAlpha的備查表
constuint8_tc_chAlphaA4Table[16] = {
0,17,34,51,
    68, 85, 102, 119,
    136, 153, 170, 187,
    204, 221, 238, 255
};


// 8位 Alpha 對應 8bit Alpha的備查表
constuint8_tc_chAlphaA8Table[256] = {
    0,1,2,3...255,
};

另外,別問我為啥有這么傻的代碼,LVGL源代碼中就有,而且非常合理。

https://github.com/lvgl/lvgl/blob/master/src/draw/sw/lv_draw_sw_letter.c

43c9362785917dbea8baa0b415bc7327.png

所以,就不要質(zhì)疑這里的合理性——我也只是舉個例子,作為技術(shù)介紹,能簡單的把事情講清楚,用簡單的例子無可厚非,領(lǐng)會精神即可。

理想中,如果有一個可重入的頭文件 mf_u8_fill_dec.h,它接受三個宏作為輸入參數(shù)

MFUNC_IN_START——起始數(shù)字

MFUNC_IN_DELTA——間隔

MFUNC_IN_COUNT——填充的數(shù)量

那么上述代碼完全可以改寫成以下的形式:

//2位Alpha 對應 8bit Alpha的備查表
constuint8_tc_chAlphaA4Table[4]={


#define MFUNC_IN_START    0
#defineMFUNC_IN_COUNT4
#defineMFUNC_IN_DELTA(255/(MFUNC_COUNT-1))
#include “mf_u8_fill_dec.h”


};


//4位Alpha對應8bitAlpha的備查表
constuint8_tc_chAlphaA4Table[16] = {


#define MFUNC_IN_START    0
#define MFUNC_IN_COUNT    16
#defineMFUNC_IN_DELTA(255/(MFUNC_COUNT-1))
#include “mf_u8_fill_dec.h”


};


// 8位 Alpha 對應 8bit Alpha的備查表
constuint8_tc_chAlphaA8Table[256] = {


#define MFUNC_IN_START    0
#defineMFUNC_IN_COUNT256
#define MFUNC_IN_DELTA    (255 / (MFUNC_COUNT - 1))
#include “mf_u8_fill_dec.h”


};

是不是簡單多了?——苦力活讓預編譯器去做,我們只管描述任務本身即可。

那么要如何實現(xiàn)mf_u8_fill_dec.h呢?這就離不開“可重入頭文件”的固定結(jié)構(gòu)了。

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

可重入頭文件的基本結(jié)構(gòu)一般固定為5個分區(qū),如下圖所示:

f112ecc9f099fb3f05c4b981a7a06c75.png

文檔區(qū):主要用于放置頭文件使用說明,當然,也包括可選的License和版本信息等;

輸入?yún)?shù)檢查區(qū):對作為輸入?yún)?shù)的宏進行必要的檢測,比如:

如果用戶忘記定義某些可選參數(shù)時提供默認值

如果用戶忘記定義某些必填的參數(shù)時,提供錯誤提示

如果用戶給的輸入?yún)?shù)非法時,提供錯誤提示

#undef 區(qū):對功能區(qū)里會定義的宏首先進行無腦 undef

功能區(qū):實現(xiàn)具體功能的區(qū)域,一般會包含如下的內(nèi)容:

定義一些宏、帶參數(shù)的宏等等

進行條件編譯

包含其它頭文件,或者進行遞歸包含

垃圾清理區(qū):主要用于清理頭文件所產(chǎn)生的宏垃圾,其中包括:

【可選】根據(jù)情況決定是否#undef作為輸入?yún)?shù)的宏

【可選】清除一些在功能區(qū)產(chǎn)生的、不希望暴露給用戶的宏

可重入頭文件的五個區(qū)域,拋開文檔區(qū),也就只剩下4個,看起來似乎并不復雜。下面我們就以mf_u8_fill_dec.h 為例,手把手帶大家建立一個麻雀雖小五臟俱全的可重入頭文件:

第一步:對輸入?yún)?shù)進行檢查(設計輸入?yún)?shù)檢查區(qū))

如前面例子中所介紹的那樣,mf_u8_fill_dec.h 包含了三個參數(shù):

MFUNC_IN_START——起始數(shù)字

MFUNC_IN_DELTA——間隔

MFUNC_IN_COUNT——填充的數(shù)量

由于并不復雜,我們可以簡單的構(gòu)建出如下的代碼:

#ifndef MFUNC_IN_START   
#defineMFUNC_IN_START0/*默認從0 開始 */
#endif
#ifndef MFUNC_IN_DELTA
#defineMFUNC_IN_DELTA    1    /* 默認以 1 為間隔 */
#endif
#ifndef MFUNC_IN_COUNT
/* 連數(shù)量都不提供,這就不能忍了!*/
#    error "Please at least define MFUNC_COUNT!!!"
#endif

這里,MFUNCMacro Function(宏函數(shù))的縮寫,IN表示這是輸入?yún)?shù)。

第二步:編寫功能(實現(xiàn)功能區(qū))

由于無法事先知道功能區(qū)會定義哪些宏,因此無法在“#undef區(qū)”進行清理,索性直接跳過,進入功能的實現(xiàn)——完成以后,再回頭編寫“#undef區(qū)”就是水到渠成了。 對mf_u8_fill_dec.h來說,它是一個典型的循環(huán)體結(jié)構(gòu),由于C語言的預編譯器并沒有提供類似 FOR之類的循環(huán)支持,我們的可以通過“用遞歸來模擬迭代”的方式來實現(xiàn)一個循環(huán),基本思路如下:

通過mf_u8_dec2str.h來維護一個計數(shù)器

只要計數(shù)器值不為0,就遞歸調(diào)用頭文件

如果計數(shù)器為0,則退出頭文件

對應代碼如下:


/* 如果計數(shù)器為0就退出 */
#if MFUNC_IN_COUNT


/* 實現(xiàn) MFUNC_IN_COUNT-- */
// MFUNC_IN_U8_DEC_VALUE = MFUNC_IN_COUNT - 1; 給腳本提供輸入
#define MFUNC_IN_U8_DEC_VALUE    (MFUNC_IN_COUNT - 1)
#include "mf_u8_dec2str.h"
#undef MFUNC_IN_COUNT
//! MFUNC_IN_COUNT = MFUNC_OUT_DEC_STR; 獲得腳本輸出
#define MFUNC_IN_COUNT    MFUNC_OUT_DEC_STR


#include"mf_u8_fill_dec.h"


#endif
對一個循環(huán)來說,我們一定有一個循環(huán)體。這里的技巧是,將循環(huán)體放置在遞歸調(diào)用的后面,換句話說:我們的做法是先一口氣積攢足夠的遞歸深度,然后在逐層返回的過程中執(zhí)行循環(huán)體。這樣做的好處是不用擔心循環(huán)的終止條件了——因此次數(shù)就是遞歸深度,這已經(jīng)固定了。 在這個例子中,循環(huán)體要做的事情就是以固定間隔填充數(shù)值,因此,當我們從遞歸的最深處逐層返回時,我們要做的就是維護填充數(shù)值,實現(xiàn)類似:
FUNC_IN_START += FUNC_IN_DELTA
這樣的功能。具體代碼為:
/* 如果計數(shù)器為0就退出 */
#if MFUNC_IN_COUNT


/* 實現(xiàn) MFUNC_IN_COUNT-- */
// MFUNC_IN_U8_DEC_VALUE = MFUNC_IN_COUNT - 1; 給腳本提供輸入
#define MFUNC_IN_U8_DEC_VALUE    (MFUNC_IN_COUNT - 1)
#include "mf_u8_dec2str.h"
#undef MFUNC_IN_COUNT
//! MFUNC_IN_COUNT = MFUNC_OUT_DEC_STR; 獲得腳本輸出
#define MFUNC_IN_COUNT    MFUNC_OUT_DEC_STR


#include "mf_u8_fill_dec.h"




/*Loop body begin ------------------------------- */
MFUNC_IN_START,


/* 實現(xiàn) FUNC_IN_START += FUNC_IN_DELTA */
#define MFUNC_IN_U8_DEC_VALUE    (MFUNC_IN_START + MFUNC_IN_DELTA)
#include "mf_u8_dec2str.h"
#undef MFUNC_IN_START
#define MFUNC_IN_START    MFUNC_OUT_DEC_STR
/* Loop Body End --------------------------------- */


#endif

第三步:更新 #undef區(qū)

通過觀察,發(fā)現(xiàn)功能區(qū)并沒有定義什么新的宏,因此略過此步驟。 第四步:清理垃圾(更新垃圾清理區(qū)) 在這個例子中,由于我們是通過遞歸返回的方法來實現(xiàn)功能,因此不能在尾部 #undef 關(guān)鍵的兩個參數(shù)MFUNC_IN_STARTMFUNC_IN_DELTA,但我們卻可以清理輸入?yún)?shù) MFUNC_IN_COUNT

#undef MFUNC_IN_COUNT

第五步:添加使用說明(更新文檔區(qū))

注意到 三個輸入?yún)?shù)中的兩個需要用戶在使用前自行#undef,因此應該將這一條關(guān)鍵信息寫入文檔區(qū)——并最好提供一個范例代碼。

至此,我們就獲得了一個可以進行數(shù)據(jù)填充的可重入宏,其完整代碼如下:

/* 
How To Use
1. Please #undef macros MFUNC_IN_START and MFUNC_IN_DELTA before using
2. [optional]Define macro MFUNC_IN_START to specify the starting value
3. [optional]Define macro MFUNC_IN_DELTA to specify the increasing step
4. Define macro MFUNC_IN_COUNT to specify the number of items. 


NOTE: the MFUNC_IN_COUNT should not larger than 200


// 4位 Alpha 對應 8bit Alpha的備查表
const uint8_t c_chAlphaA4Table[16] = {


#undef MFUNC_IN_START
#undef MFUNC_IN_DELTA


#define MFUNC_IN_START    0
#define MFUNC_IN_COUNT    16
#define MFUNC_IN_DELTA    17
#include "mf_u8_fill_dec.h"


};
*/


#ifndef MFUNC_IN_START   
#   define MFUNC_IN_START    0    /* 默認從 0 開始 */
#endif
#ifndef MFUNC_IN_DELTA
#   define MFUNC_IN_DELTA    1    /* 默認以 1 為間隔 */
#endif
#ifndef MFUNC_IN_COUNT
/* 連數(shù)量都不提供,這就不能忍了!*/
#    error "Please at least define MFUNC_COUNT!!!"
#endif


/* 如果計數(shù)器為0就退出 */
#if MFUNC_IN_COUNT


/* 實現(xiàn) MFUNC_IN_COUNT-- */
// MFUNC_IN_U8_DEC_VALUE = MFUNC_IN_COUNT - 1; 給腳本提供輸入
#define MFUNC_IN_U8_DEC_VALUE    (MFUNC_IN_COUNT - 1)
#include "mf_u8_dec2str.h"
#undef MFUNC_IN_COUNT
//! MFUNC_IN_COUNT = MFUNC_OUT_DEC_STR; 獲得腳本輸出
#define MFUNC_IN_COUNT    MFUNC_OUT_DEC_STR


#include "mf_u8_fill_dec.h"




/* Loop body begin ------------------------------- */
MFUNC_IN_START,


/* 實現(xiàn) FUNC_IN_START += FUNC_IN_DELTA */
#define MFUNC_IN_U8_DEC_VALUE    (MFUNC_IN_START + MFUNC_IN_DELTA)
#include "mf_u8_dec2str.h"
#undef MFUNC_IN_START
#define MFUNC_IN_START    MFUNC_OUT_DEC_STR


/* Loop body End --------------------------------- */


#endif


#undef MFUNC_IN_COUNT

別忘記根據(jù)使用說明,對例子代碼進行適當?shù)男薷模?/p>

// 2位 Alpha 對應 8bit Alpha的備查表
const uint8_t c_chAlphaA4Table[4] = {


#undefMFUNC_IN_START
#undef MFUNC_IN_DELTA


#define MFUNC_IN_START    0
#define MFUNC_IN_COUNT    4
#define MFUNC_IN_DELTA    85
#include "mf_u8_fill_dec.h"


};


// 4位 Alpha 對應 8bit Alpha的備查表
const uint8_t c_chAlphaA4Table[16] = {


#undefMFUNC_IN_START
#undef MFUNC_IN_DELTA


#define MFUNC_IN_START    0
#define MFUNC_IN_COUNT    16
#define MFUNC_IN_DELTA    17
#include "mf_u8_fill_dec.h"


};




// 8位 Alpha 對應 8bit Alpha的備查表
const uint8_t c_chAlphaA8Table[256] = {


#undef MFUNC_IN_START
#undef MFUNC_IN_DELTA


#define MFUNC_IN_START    0
#define MFUNC_IN_COUNT    128
#include "mf_u8_fill_dec.h"


#undef MFUNC_IN_START
#undef MFUNC_IN_DELTA


#define MFUNC_IN_START    128
#define MFUNC_IN_COUNT    128
#include "mf_u8_fill_dec.h"


};

大功告成!

【說在后面的話】

受到篇幅限制,本文只介紹了“可重入頭文件”的兩種常見形式,并著重介紹了以“遞歸”方式來批量進行代碼生成的例子。

雖然填充數(shù)組看起來用處并不很大,但它充分展示了通過可重入頭文件進行指定次數(shù)遞歸的方法。相信只要打開了思路,我對大家舉一反三的能力從不懷疑。

需要強調(diào)一下:可重入頭文件只是一類非常基本的方法,并不是所謂的旁門左道,其構(gòu)建方式有固定的方法,且有章可循,人人都能掌握

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

    關(guān)注

    180

    文章

    7575

    瀏覽量

    134233
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4672

    瀏覽量

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

    關(guān)注

    0

    文章

    24

    瀏覽量

    9817

原文標題:【為宏正名】99%的人從第一天學習C語言就自廢的武功

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

收藏 人收藏

    評論

    相關(guān)推薦

    請問TIVA有使用結(jié)構(gòu)體定義的頭文件

    TIVA有使用結(jié)構(gòu)體定義的頭文件嗎,類似C2000那樣的頭文件?IAR頭文件是用結(jié)構(gòu)體定義的,但是不知道怎么用。
    發(fā)表于 08-14 06:31

    stm32頭文件多次調(diào)用重復包含解決方法

    stm32頭文件多次調(diào)用重復包含解決方法使用條件編譯預處理程序提供條件編譯的功能。可以按不同的條件去編譯不同的程序部分,產(chǎn)生不同的目標代碼文件。使用條件語句進行編譯,生成的目標代碼程序較長,而采用
    發(fā)表于 08-23 06:51

    STM32源文件頭文件代碼層次結(jié)構(gòu)

    STM32工程文件目錄結(jié)構(gòu)1、USER–用戶文件2、HARDWARE–外部硬件相關(guān)的驅(qū)動函數(shù)3、SYSTEM–工程中常用到的代碼4、CORE–固件庫核心文件和啟動
    發(fā)表于 08-24 07:11

    WIN7添加攝像頭文件

    這是WIN7添加攝像頭文件,這是WIN7添加攝像頭文件,這是WIN7添加攝像頭文件,這是WIN7添加攝像頭文件,這是WIN7添加攝像頭文件
    發(fā)表于 12-24 15:12 ?4次下載

    基于TMS320F28027的頭文件文件

    基于TMS320F28027的頭文件文件
    發(fā)表于 12-29 17:25 ?53次下載

    四極汽輪發(fā)電機定子繞組端部固定結(jié)構(gòu)振動模態(tài)研究

    四極汽輪發(fā)電機定子繞組端部固定結(jié)構(gòu)振動模態(tài)研究_劉文博
    發(fā)表于 01-01 15:44 ?0次下載

    AVR_Mega128液晶電路的頭文件代碼

    大家使用時把這段代碼保存成.h的頭文件,在主程序中包含這個頭文件就行
    發(fā)表于 09-01 11:07 ?8次下載

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

    12.3 在C target=_blank style=cursor:pointer;color:#D05C38;text-decoration:underline;》C++中使用C頭文件 本節(jié)描述
    發(fā)表于 10-19 09:24 ?3次下載

    使用KEIL開發(fā)51單片機時出現(xiàn)頭文件重復定義的錯誤應該如何解決

    發(fā)現(xiàn)這些51單片機的寄存器都重復定義,一開始懷疑是不是reg51.h頭文件的引入方式有問題,但是有時候正常,有時候又出現(xiàn)錯誤,甚是疑惑。其實出現(xiàn)的錯誤的原因很簡單,就是因為你工程里面的文件中有的
    發(fā)表于 09-05 17:27 ?5次下載
    使用KEIL開發(fā)51單片機時出現(xiàn)<b class='flag-5'>頭文件</b>報<b class='flag-5'>重復</b>定義的錯誤應該如何解決

    C語言頭文件是做什么的

    c語言程序代碼文件擴展名只能是.c或者.h,換句話說,c語言程序代碼只能在擴展名為.c或者.h的文件中編寫。我們把.h文件稱為頭文件頭文件
    的頭像 發(fā)表于 02-13 15:29 ?9251次閱讀

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

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

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

    說明本文假定讀者已具備基本的C編譯知識。 如非特殊說明,文中“源文件”指 * .c文件,“頭文件”指 *.h文件,“引用”指包含頭文件
    的頭像 發(fā)表于 11-14 11:31 ?3398次閱讀

    單片機-頭文件

    reg52.h頭文件的作用在代碼中引用頭文件,其實際意義是將頭文件中的所用內(nèi)容都放到引用頭文件的地方下面是reg52.h頭文件的內(nèi)容:/*-
    發(fā)表于 11-23 17:21 ?17次下載
    單片機-<b class='flag-5'>頭文件</b>

    MCU_頭文件編寫

    頭文件中一般放一些重復使用的代碼,如:常量、變量、宏等的定義,函數(shù)的聲明。當使用#include語句引用頭頭文件時,相當于將頭文件中的內(nèi)容復制到#include處。
    發(fā)表于 12-05 10:36 ?5次下載
    MCU_<b class='flag-5'>頭文件</b>編寫

    STM32重要源文件頭文件說明

    STM32重要源文件頭文件說明
    發(fā)表于 12-05 18:21 ?28次下載
    STM32重要源<b class='flag-5'>文件</b>和<b class='flag-5'>頭文件</b>說明