www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁(yè) > 公眾號(hào)精選 > 小麥大叔
[導(dǎo)讀]【說(shuō)在前面的話】 市面上大部分C程序員對(duì)宏存在巨大的誤解甚至是恐懼,并因此極力避免宏的適度使用,甚至將宏在封裝中發(fā)揮正確作用的行為視作是對(duì)C語(yǔ)言的“背叛”——震驚之余,對(duì)于為什么大家會(huì)有這種想法的原因,我曾經(jīng)一度是非常“傲慢的”,這種傲慢與某

【說(shuō)在前面的話】

市面上大部分C程序員對(duì)宏存在巨大的誤解甚至是恐懼,并因此極力避免宏的適度使用,甚至將宏在封裝中發(fā)揮正確作用的行為視作是對(duì)C語(yǔ)言的“背叛”——震驚之余,對(duì)于為什么大家會(huì)有這種想法的原因,我曾經(jīng)一度是非?!鞍谅摹?,這種傲慢與某些人宣稱“窮人都是因?yàn)閼兴圆鸥F”時(shí)所表現(xiàn)出的那種態(tài)度并無(wú)任何本質(zhì)不同——然而我錯(cuò)了,在閑暇之余認(rèn)真看了不少經(jīng)典的C語(yǔ)言教材后我才意識(shí)到:

不是讀者普遍懶或者輕視教材中有關(guān)宏的內(nèi)容,而是那些對(duì)宏來(lái)說(shuō)如同“加法交換律、結(jié)合律”一樣的基本規(guī)則和知識(shí)并沒(méi)有認(rèn)真且完整的出現(xiàn)在教科書中!


這是何等的“呵呵”。這下全都清楚了:
  • 為什么大家會(huì)那么懼怕宏的使用

  • 定義宏的時(shí)候,為什么遇到哪怕很基本的小問(wèn)題也根本無(wú)從下手;

  • 為什么那么多人聲稱系統(tǒng)提供的諸如 __LINE__ 之類的宏時(shí)好時(shí)壞

  • 為什么很多關(guān)于宏的正常使用被稱為奇技淫巧……


真是哭笑不得。這些規(guī)則是如此簡(jiǎn)單,介紹一下根本無(wú)需多么復(fù)雜的篇幅。接下來(lái),讓我們簡(jiǎn)單的學(xué)習(xí)一下這些本應(yīng)該寫入教科書中的基本內(nèi)容。注意,這與你們?cè)谄渌娞?hào)里學(xué)到的關(guān)于某些宏的基本使用方法是兩回事。


【宏不屬于C語(yǔ)言】


說(shuō)“宏不屬于C語(yǔ)言”是一種夸張的說(shuō)法,但卻非常反映問(wèn)題的本質(zhì)和基本事實(shí):
  • C語(yǔ)言的編譯分為三個(gè)階段:預(yù)編譯階段、編譯階段和鏈接階段。正如上圖所示的那樣,預(yù)編譯階段的產(chǎn)物是單個(gè)的“.c”文件;編譯階段將這些“.c”文件一個(gè)一個(gè)彼此獨(dú)立的編譯為對(duì)應(yīng)的對(duì)象("*.obj")文件;這些對(duì)象文件就像樂(lè)高積木一樣會(huì)在最終的鏈接階段按照事先約定好的圖紙(地址空間布局描述文件,又稱linker script或者scatter script)被linker組裝到一起,最終生成在目標(biāo)機(jī)器上可以運(yùn)行的鏡像文件。


  • 宏僅在預(yù)編譯階段有效,它的本質(zhì)只是文字替換。在完成預(yù)編譯處理以后,進(jìn)入編譯階段的.c實(shí)際上已經(jīng)不存在任何“宏”、條件編譯、“#include”以及"#pragma"之類的預(yù)編譯內(nèi)容——此時(shí)的C源文件是一個(gè)純粹且獨(dú)立的文本文件。很多編譯器在命令行下都提供一個(gè)"-E"的選項(xiàng),它其實(shí)就是告訴編譯器,只進(jìn)行預(yù)編譯操作并停在這里。此時(shí),編譯的結(jié)果就是大家所說(shuō)的“宏展開”后的內(nèi)容。學(xué)會(huì)使用"-E"選項(xiàng),是檢測(cè)自己縮寫的宏是否正確的最有效工具。


知道這一知識(shí)有什么用呢?首先,你會(huì)明白,宏本身是與C語(yǔ)言的其它語(yǔ)法毫無(wú)關(guān)聯(lián)的。 宏有自己的語(yǔ)法,且非常簡(jiǎn)單。 在進(jìn)行宏展開的時(shí)候,編譯器并不會(huì)去進(jìn)行任何宏以外的C語(yǔ)言語(yǔ)法檢查、甚至根本不知道C語(yǔ)言語(yǔ)法。實(shí)際上,有大量C語(yǔ)言老鳥特別喜歡在其它C語(yǔ)言以外的文本文件里使用“宏”(其實(shí)還有條件編譯之類的),最典型的例子就是在Arm Compiler 6的scatter-script中用宏來(lái)定義一些地址常數(shù):
#! armclang --target=arm-arm-none-eabi -march=armv6-m -E -x c#define ADDRESS 0x20000000#include "include_file_1.h"LR1 ADDRESS{}

這里,第一行的命令行:

#! armclang --target=arm-arm-none-eabi -march=armv6-m -E -x c

就是告訴linker,在處理scatter-script之前要執(zhí)行“#!” 后面的命令行,這里的"-E"就是告訴armclang:“我們只進(jìn)行預(yù)編譯”——也就是"#include"以及宏替換之類的工作——所以宏“ADDRESS” 會(huì)被替換會(huì) 0x20000000,而"include_file_1.h" 中的內(nèi)容也會(huì)被加入到當(dāng)前的scatter-script文件中來(lái)。



需要強(qiáng)調(diào)下,在這個(gè)例子中,放在第一行“#!”后面的命令行之所以為會(huì)被linker自動(dòng)執(zhí)行,是因?yàn)閘inker就是這么使用 “.sct” 文件的。對(duì)于其它想使用C語(yǔ)言宏對(duì)任意文本文件進(jìn)行預(yù)處理的場(chǎng)合,需要自己動(dòng)手編寫命令行和腳本。比如,如果你想在 perl 里使用 C語(yǔ)言的預(yù)編譯,那么就需要你在執(zhí)行目標(biāo) .pl 文件前,先用C語(yǔ)言編譯器對(duì)其進(jìn)行一次預(yù)編譯。



總的來(lái)說(shuō),“宏不屬于C語(yǔ)言”并非空穴來(lái)風(fēng),事實(shí)上, 只要你有興趣去寫腳本,包括宏在內(nèi)的所有預(yù)編譯語(yǔ)法可以在一切文本文件中使用。

知道這一知識(shí)的另外一個(gè)作用就是回答每一個(gè)C語(yǔ)言初學(xué)者都繞不開的經(jīng)典問(wèn)題:“宏和枚舉有啥區(qū)別”?有啥區(qū)別?這區(qū)別老大了:
  • 正如前面所說(shuō)的,宏只存在于“預(yù)編譯階段”,而活不到“編譯階段”;宏是沒(méi)有任何C語(yǔ)法意義的

  • 枚舉與之相反,只存在于“編譯階段”,是具有嚴(yán)格的C語(yǔ)法意義的——它的每一個(gè)成員都明確代表一個(gè)整形常量值


其實(shí),從宏和枚舉服務(wù)的階段看來(lái),他們是老死不相往來(lái)的。那么具體在使用時(shí),這里的區(qū)別表現(xiàn)在什么地方呢?我們來(lái)看一個(gè)例子:

#define USART_COUNT 4
#if USART_COUNT > 0extern uint8_t s_chUSARTBuffer[USART_COUNT];#endif

這里例子意圖很簡(jiǎn)單,根據(jù)宏USART_COUNT的值來(lái)?xiàng)l件編譯。如果我們把USART_COUNT換成枚舉就不行了:

typedef enum { /* list all the available USART here */    USART0_idx = 0,    USART1_idx,    USART2_idx,    USART3_idx,    /* number of USARTs*/    USART_COUNT,}usart_idx_t;
#if USART_COUNT > 0extern uint8_t s_chUSARTBuffer[USART_COUNT];#endif

在這個(gè)例子里,USART_COUNT的值會(huì)隨著前面列舉的UARTx_idx的增加而自動(dòng)增加——作為一個(gè)技巧——精確的表示當(dāng)前實(shí)際有效的USART數(shù)量,從意義上說(shuō)嚴(yán)格貼合了 USART_COUNT 這個(gè)名稱的意義。這個(gè)代碼看似沒(méi)有問(wèn)題,但實(shí)際上根據(jù)前面的知識(shí)我們知道:條件編譯是在“預(yù)編譯階段”進(jìn)行的、枚舉是在“編譯階段”才有意義。換句話說(shuō),當(dāng)下面代碼判斷枚舉USART_COUNT的時(shí)候,預(yù)編譯階段根本不認(rèn)識(shí)它是誰(shuí)(預(yù)編譯階段沒(méi)有任何C語(yǔ)言的語(yǔ)法知識(shí))——這時(shí)候USART_COUNT作為枚舉還沒(méi)出生呢!

#if USART_COUNT > 0extern uint8_t s_chUSARTBuffer[USART_COUNT];#endif

同樣道理,如果你想借助下面的宏來(lái)生成代碼,得到的結(jié)果會(huì)出人意料:

typedef enum { /* list all the available USART here */ USART0_idx = 0, USART1_idx, USART2_idx, USART3_idx, /* number of USARTs*/ USART_COUNT,}usart_idx_t;
extern int usart0_init(void);extern int usart1_init(void);extern int usart2_init(void);extern int usart3_init(void);
#define USART_INIT(__USART_INDEX) \    usart##__USART_INDEX##_init()

應(yīng)用中,我們期望配合UARTn_idx與宏USART_INIT一起使用:

...USART_INIT(USART1_idx);...

借助宏的膠水運(yùn)算“##”,我們期望的結(jié)果是:

...usart1_init();...

由于同樣的原因——在進(jìn)行宏展開的時(shí)候,枚舉還沒(méi)有“出生”——實(shí)際展開的效果是這樣的:

...usartUSART1_idx_init();...

由于函數(shù)  usartUSART1_idx_init() 并不存在,所以在鏈接階段linker會(huì)報(bào)告類似“undefined symbol usartUSART1_idx_init()”——簡(jiǎn)單說(shuō)就是找不到函數(shù)。要解決這一問(wèn)題也很簡(jiǎn)單,直接把枚舉用宏來(lái)定義就可以了:


#define USART_COUNT 4
#if USART_COUNT > 0extern int usart0_init(void);#   define USART0_idx 0#endif
#if USART_COUNT > 1extern int usart1_init(void);# define USART1_idx 1#endif
#if USART_COUNT > 2extern int usart2_init(void);# define USART2_idx 2#endif
#if USART_COUNT > 3extern int usart3_init(void);# define USART3_idx 3#endif


那么是不是說(shuō),宏就比枚舉好呢?當(dāng)然不是,準(zhǔn)確的說(shuō)法應(yīng)該是: 在誰(shuí)的地盤誰(shuí)的優(yōu)點(diǎn)就突出。我們說(shuō)枚舉僅在編譯階段有效、它具有明確的語(yǔ)法意義(具體語(yǔ)法意義請(qǐng)參考相應(yīng)的C語(yǔ)言教材)。相對(duì)宏來(lái)說(shuō),怎么理解枚舉的好處呢?
  • 枚舉可以被當(dāng)作類型來(lái)使用,并定義枚舉變量——宏做不到;

  • 當(dāng)使用枚舉作為函數(shù)的形參或者是switch檢測(cè)的目標(biāo)時(shí),有些比較“智能”的C編譯器會(huì)在編譯階段把枚舉作為參考進(jìn)行“強(qiáng)類型”檢測(cè)——比如檢查函數(shù)傳遞過(guò)程中你給的值是否是枚舉中實(shí)際存在的;又比如在switch中是否所有的枚舉條目都有對(duì)應(yīng)的case(在省缺default的情況下)。

  • 除IAR以外,保存枚舉所需的整型在一個(gè)編譯環(huán)境中是相對(duì)來(lái)說(shuō)較為確定的(不是short就是int)——在這種情況下,枚舉的常量值就具有了類型信息,這是用宏表示常量時(shí)所不具備的。

  • 少數(shù)IDE只能對(duì)枚舉進(jìn)行語(yǔ)法提示而無(wú)法對(duì)宏進(jìn)行語(yǔ)法提示。



【宏的本質(zhì)和替換規(guī)則】

很多人都知道宏的本質(zhì)是文字替換,也就是說(shuō), 預(yù)編譯過(guò)程中宏會(huì)被替換成對(duì)應(yīng)的字符串;然而在這一過(guò)程中所遵守的關(guān)鍵規(guī)則,很多人就不清楚了。

首先,針對(duì)一個(gè)沒(méi)有被定義過(guò)的宏:

  • 在#ifdef、#ifndef 以及 defined() 表達(dá)式中,它可以正確的返回boolean量——確切的表示它沒(méi)有被定義過(guò);

  • 在#if 中被直接使用(沒(méi)有配合defined()),則很多編譯器會(huì)報(bào)告warning,指出這是一個(gè)不存在的宏,同時(shí)默認(rèn)它的值是boolean量的false——而并不保證是"0";

  • 在除以上情形外的其它地方使用,比如在代碼中使用,則它會(huì)被作為代碼的一部分原樣保留到編譯階段——而不會(huì)進(jìn)行任何操作;通常這會(huì)在鏈接階段觸發(fā)“undefined symbol”錯(cuò)誤——這是很自然的,因?yàn)槟阋詾槟阍谟煤辏ㄖ徊贿^(guò)因?yàn)槟阃浂x了,或者沒(méi)有正確include所需的頭文件),編譯器卻以為你在說(shuō)函數(shù)或者變量——當(dāng)然找不到了。


舉個(gè)例子,宏 __STDC_VERSION__ 可以被用來(lái)檢查當(dāng)前ANSI-C的標(biāo)準(zhǔn):

#if __STD_VERSION__ >= 199901L/* support C99 */#  define SAFE_ATOM_CODE(...)             \ {                            \ uint32_t wTemp = __disable_irq(); \      __VA_ARGS__;                       \      __set_PRIMASK(wTemp);            \ }#else/* doesn't support C99, assume C89/90 */#  define SAFE_ATOM_CODE(__CODE)         \ { \ uint32_t wTemp = __disable_irq(); \ __CODE; \ __set_PRIMASK(wTemp); \ }#endif

上述寫法在支持C99的編譯器中是不會(huì)有問(wèn)題的,因?yàn)?nbsp;__STDC_VERSION__ 一定會(huì)由編譯器預(yù)先定義過(guò);而同樣的代碼放到僅支持C89/90的環(huán)境中就有可能會(huì)出問(wèn)題,因?yàn)?nbsp;__STDC_VERSION__ 并不保證一定會(huì)被事先定義好(C89/90并沒(méi)有規(guī)定要提供這個(gè)宏),因此 __STDC_VERSION__ 就有可能成為一個(gè)未定義的宏,從而觸發(fā)編譯器的warning。為了修正這一問(wèn)題,我們需要對(duì)上述內(nèi)容進(jìn)行適當(dāng)?shù)男薷模?br>

#if defined(__STD_VERSION__) && __STD_VERSION__ >= 199901L/* support C99 */...#else/* doesn't support C99, assume C89/90 */...#endif


其次,定義宏的時(shí)候,如果只給了名字卻沒(méi)有提供內(nèi)容:
  • 在#ifdef、#ifndef 以及 defined() 表達(dá)式中,它可以正確的返回boolean量——確切的表示它被定義了;

  • 在#if 中被直接使用(沒(méi)有配合defined()),編譯器會(huì)把它看作“空”;在一些數(shù)值表達(dá)式中,它會(huì)被默認(rèn)當(dāng)作“0”,沒(méi)有任何警告信息會(huì)被產(chǎn)生

  • 在除以上情形外的其它地方使用,比如在代碼中使用,編譯器會(huì)把它看作“空字符串”(注意,這里不包含引號(hào))——它不會(huì)存活到編譯階段;


最后,我們來(lái)說(shuō)一個(gè)容易被人忽視的結(jié)論:
  • 第一條:任何使用到膠水運(yùn)算“##”對(duì)形參進(jìn)行粘合的參數(shù)宏,一定需要額外的再套一層

  • 第二條:其余情況下,如果要用到膠水運(yùn)算,一定要在內(nèi)部借助參數(shù)宏來(lái)完成粘合過(guò)程


為了理解這一“結(jié)論”,我們不妨舉一個(gè)例子:在前面的代碼中,我們定義過(guò)一個(gè)用于自動(dòng)關(guān)閉中斷并在完成指定操作后自動(dòng)恢復(fù)原來(lái)狀態(tài)的宏:
#define SAFE_ATOM_CODE(...)               \ { \ uint32_t wTemp = __disable_irq(); \ __VA_ARGS__; \ __set_PRIMASK(wTemp); \ }

由于這里定義了一個(gè)變量wTemp,而如果用戶插入的代碼中也使用了同名的變量,就會(huì)產(chǎn)生很多問(wèn)題:輕則編譯錯(cuò)誤(重復(fù)定義);重則出現(xiàn)局部變量wTemp強(qiáng)行取代了用戶自定義的靜態(tài)變量的情況,從而直接導(dǎo)致系統(tǒng)運(yùn)行出現(xiàn)隨機(jī)性的故障(比如隨機(jī)性的中斷被關(guān)閉后不再恢復(fù),或是原本應(yīng)該被關(guān)閉的全局中斷處于打開狀態(tài)等等)。為了避免這一問(wèn)題,我們往往會(huì)想自動(dòng)給這個(gè)變量一個(gè)不會(huì)重復(fù)的名字,比如借助 __LINE__ 宏給這一變量加入一個(gè)后綴:

#define SAFE_ATOM_CODE(...)                \ { \      uint32_t wTemp##__LINE__ = __disable_irq();    \ __VA_ARGS__; \ __set_PRIMASK(wTemp); \ }
一個(gè)使用例子:
...SAFE_ATOM_CODE(    /* do something here */    ...)...
假設(shè)這里 SAFE_ATOM_CODE 所在行的行號(hào)是 123,那么我們期待的代碼展開是這個(gè)樣子的(我重新縮進(jìn)過(guò)了):
...  {                                                         uint32_t wTemp123 = __disable_irq();           __VA_ARGS__;                                          __set_PRIMASK(wTemp);                            }...
然而,實(shí)際展開后的內(nèi)容是這樣的:
...  {                                                         uint32_t wTemp__LINE__ = __disable_irq();           __VA_ARGS__;                                          __set_PRIMASK(wTemp);                            }...
這里, __LINE__似乎并沒(méi)有被正確替換為123,而是以原樣的形式與wTemp粘貼到了一起——這就是很多人經(jīng)常抱怨的 __LINE__ 宏不穩(wěn)定的問(wèn)題。實(shí)際上,這是因?yàn)樯鲜龊甑臉?gòu)建沒(méi)有遵守前面所列舉的兩條結(jié)論導(dǎo)致的。

從內(nèi)容上看,SAFE_ATOM_CODE() 要粘合的對(duì)象并不是形參,根據(jù)結(jié)論第二條,需要借助另外一個(gè)參數(shù)宏來(lái)幫忙完成這一過(guò)程。為此,我們需要引入一個(gè)專門的宏:

#define CONNECT2(__A, __B) __A##__B
注意到,這個(gè)參數(shù)宏要對(duì)形參進(jìn)行膠水運(yùn)算,根據(jù)結(jié)論第一條,需要在宏的外面再套一層,因此,修改代碼得到:
#define __CONNECT2(__A, __B) __A##__B#define CONNECT2(__A, __B) __CONNECT2(__A, __B)
#define __CONNECT3(__A, __B, __C)    __A##__B##__C#define CONNECT2(__A, __B, __C) __CONNECT3(__A, __B, __C)
修改前面的定義得到:
#define SAFE_ATOM_CODE(...)                \ { \      uint32_t CONNECT2(wTemp,__LINE__) =              \       __disable_irq();      \ __VA_ARGS__; \ __set_PRIMASK(wTemp); \ }
有興趣的朋友可以通過(guò) "-E" 可以觀察到 __LINE__ 被正確的展開了。

【宏是引用而非變量】

具體實(shí)踐中, 很多人在使用宏過(guò)程中會(huì)產(chǎn)生“宏是一種變量”的錯(cuò)覺(jué),這是因?yàn)闊o(wú)論一個(gè)宏此前是否定義過(guò),我們都可以借助   # un def  操作,強(qiáng)制注銷它,從而有能力重新給這一宏賦予一個(gè)新的值,例如:
#include <stdbool.h>
#undef false#undef true
#define false     0#define true      (!false)

上述例子里,在stdbool.h中,true通常被定義為1,這會(huì)導(dǎo)致很多人在編寫期望值是true的邏輯表達(dá)式時(shí),一不小心落入圈套——因?yàn)閠rue的真實(shí)含義是“非0”,這就包含了除了1以外的一切非0的整數(shù),當(dāng)用戶寫下:
if (true == xxxxx) {...}
表達(dá)式時(shí),實(shí)際獲得的是:
if (1 == xxxxx) {...}
這顯然是過(guò)于狹隘的——會(huì)出現(xiàn)實(shí)際為true卻判定為false(走else分支)的情況,為了避免這種情況, 實(shí)踐中,我們應(yīng)該避免在邏輯表達(dá)式中使用true——無(wú)論true的值是什么。


實(shí)際上, 宏的變量特性是不存在的 ,更確切地說(shuō)法是, 宏是一種“引用” 。那么什么是引用呢?《六祖壇經(jīng)》中有一個(gè)非常著名的公案,用于解釋慧能關(guān)于“不立文字”的主張,他說(shuō),通過(guò)“文字”來(lái)了解真理,就好比用手指向月亮——正如手指可以指出明月的所在,文字也的確可以用來(lái)描述真理,但畢竟手指不是明月,文字也不是真理本身,因此如果有辦法直擊真理,又如何需要執(zhí)著于文字(經(jīng)文)本身呢?我們雖然不一定要修禪,但這里手指與明月的關(guān)系恰好可以非常生動(dòng)的解釋“引用”這一概念。



我們說(shuō)宏的本質(zhì)是一個(gè)引用,那么如何理解這種說(shuō)法呢? 我們來(lái)看一個(gè)例子:
#define EXAMPLE_A          123#define EXAMPLE EXAMPLE_A
#undef  EXAMPLE_A

對(duì)于下面的代碼:

CONNECT2(uint32_t wVariable, EXAMPLE);

如果宏是一個(gè)變量,那么展開的結(jié)果應(yīng)該是:

uint32_t wVariable123;

然而,我們實(shí)際獲得的是:

uint32_t wVariableEXAMPLE_A;

如何理解這一結(jié)果呢?


如果宏是一個(gè)引用,那么當(dāng)EXAMPLE_A與123之間的關(guān)系被銷毀時(shí),原本EXAMPLE > EXAMPLE_A > 123 的引用關(guān)系就只剩下 EXAMPLE > EXAMPLE_A。又由于EXAMPLE_A已經(jīng)不復(fù)存在,因此EXAMPLE_A在展開時(shí)就被當(dāng)作是最終的字符串,與"uint32_t wVariable"連接到了一起。

?


這一知識(shí)對(duì)我們有什么幫助呢?幫助實(shí)在太大了!甚至可以把預(yù)編譯器直接變成一個(gè)腳本解釋器。受到篇幅的限制,我們無(wú)法詳細(xì)展開,就展示一個(gè)最常見的用法吧:

還記得前面定義的USART_INIT()宏么?
#define USART_INIT(__USART_INDEX) \    usart##__USART_INDEX##_init()
使用的時(shí)候,我們需要確保填寫在括號(hào)中的任何內(nèi)容都必須直接對(duì)應(yīng)一個(gè)在效范圍內(nèi)的整數(shù)(比如0~3),比如:
USART_INIT(USART1_idx);
由于USART1_idx直接對(duì)應(yīng)于字符串 “1”,因此,實(shí)際會(huì)被展開為:
usart1_init();

很多時(shí)候,我們可能會(huì)希望代碼有更多的靈活性,因此,我們會(huì)再額外定義一個(gè)宏來(lái)將某些代碼與具體的USART祛除不必要的耦合:
#include "app_cfg.h"
#ifndef DEBUG_USART# define DEBUG_USART    USART0_idx#endif
USART_INIT(DEBUG_USART);
這樣,雖然代碼默認(rèn)使用USART0作為 DEBUG_USART,但用戶完全可以通過(guò)配置文件 "app_cfg.h" 來(lái)修改這一配置。到目前為止,一切都好。但此時(shí),app_cfg.h 中的內(nèi)容已經(jīng)和模塊內(nèi)的代碼有了一定的“隔閡”——用戶不一定知道 DEBUG_USART 必須是一個(gè)有效的數(shù)字字符串,而不能是一個(gè)表達(dá)式,哪怕這個(gè)表達(dá)式會(huì)“自動(dòng)”計(jì)算出最終需要使用的值。比如,在 app_cfg.h 中,可能會(huì)出現(xiàn)以下的內(nèi)容:
/* app_cfg.h */
#define USART_MASTER_CNT 1#define USART_SLAVE_CNT     2#define DEBUG_USART (USART_MASTER_CNT + USART_SLAVE_CNT)
這里,出于某種不可抗拒原因,用戶希望永遠(yuǎn)使用最后一個(gè)USART作為 DEBUG_USART,并通過(guò)一個(gè)表達(dá)式計(jì)算出了這個(gè)USART的編號(hào)。遺憾的是,當(dāng)用戶自信滿滿的寫下這一“智能算法”后,我們得到的實(shí)際上是:
usart(1+2)_init();
對(duì)編譯器來(lái)說(shuō),這顯然不是一個(gè)有效的C語(yǔ)法,因此報(bào)錯(cuò)是在所難免。那么如何解決這一問(wèn)題呢?借助宏的引用特性,我們可以獲得如下的內(nèi)容:
#include "app_cfg.h"
#ifndef DEBUG_USART# define DEBUG_USART USART0_idx#else#   if DEBUG_USART == 0#    undef DEBUG_USART#    define DEBUG_USART    0#   elif   DEBUG_USART == 1# undef DEBUG_USART# define DEBUG_USART 1# elif DEBUG_USART == 2# undef DEBUG_USART# define DEBUG_USART 2# elif DEBUG_USART == 3# undef DEBUG_USART#       define DEBUG_USART    3#   else#   error "out of range for DEBUG_USART"#endif
進(jìn)一步思考,假設(shè)一個(gè)宏的取值范圍是 0~255,而我們想把這一宏的值切實(shí)的轉(zhuǎn)化為對(duì)應(yīng)的十進(jìn)制數(shù)字字符串,按照上面的方法,那我們豈不是要累死?且慢,我們還有別的辦法,假設(shè)輸入數(shù)值的宏叫 MFUNC_IN_U8_DEC_VALUE 首先分別獲得3位十進(jìn)制的每一位上的數(shù)字內(nèi)容:

#undef __MFUNC_OUT_DEC_DIGIT_TEMP0#undef __MFUNC_OUT_DEC_DIGIT_TEMP1#undef __MFUNC_OUT_DEC_DIGIT_TEMP2#undef __MFUNC_OUT_DEC_STR_TEMP
/* 獲取個(gè)位 */#if (MFUNC_IN_U8_DEC_VALUE % 10) == 0# define __MFUNC_OUT_DEC_DIGIT_TEMP0 0#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 1# define __MFUNC_OUT_DEC_DIGIT_TEMP0 1#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 2# define __MFUNC_OUT_DEC_DIGIT_TEMP0 2#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 3# define __MFUNC_OUT_DEC_DIGIT_TEMP0 3#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 4# define __MFUNC_OUT_DEC_DIGIT_TEMP0 4#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 5# define __MFUNC_OUT_DEC_DIGIT_TEMP0 5#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 6# define __MFUNC_OUT_DEC_DIGIT_TEMP0 6#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 7# define __MFUNC_OUT_DEC_DIGIT_TEMP0 7#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 8# define __MFUNC_OUT_DEC_DIGIT_TEMP0 8#elif (MFUNC_IN_U8_DEC_VALUE % 10) == 9# define __MFUNC_OUT_DEC_DIGIT_TEMP0 9#endif
/* 獲取十位數(shù)字 */#if ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 0# define __MFUNC_OUT_DEC_DIGIT_TEMP1 0#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 1# define __MFUNC_OUT_DEC_DIGIT_TEMP1 1#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 2# define __MFUNC_OUT_DEC_DIGIT_TEMP1 2#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 3# define __MFUNC_OUT_DEC_DIGIT_TEMP1 3#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 4# define __MFUNC_OUT_DEC_DIGIT_TEMP1 4#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 5# define __MFUNC_OUT_DEC_DIGIT_TEMP1 5#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 6# define __MFUNC_OUT_DEC_DIGIT_TEMP1 6#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 7# define __MFUNC_OUT_DEC_DIGIT_TEMP1 7#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 8# define __MFUNC_OUT_DEC_DIGIT_TEMP1 8#elif ((MFUNC_IN_U8_DEC_VALUE/10) % 10) == 9# define __MFUNC_OUT_DEC_DIGIT_TEMP1 9#endif
/* 獲取百位數(shù)字 */#if ((MFUNC_IN_U8_DEC_VALUE/100) % 10) == 0# define __MFUNC_OUT_DEC_DIGIT_TEMP2 0#elif ((MFUNC_IN_U8_DEC_VALUE/100) % 10) == 1# define __MFUNC_OUT_DEC_DIGIT_TEMP2 1#elif ((MFUNC_IN_U8_DEC_VALUE/100) % 10) == 2# define __MFUNC_OUT_DEC_DIGIT_TEMP2 2#endif
接下來(lái),我們將代表“個(gè)、十、百”的三個(gè)宏拼接起來(lái):
#if __MFUNC_OUT_DEC_DIGIT_TEMP2 == 0 # if __MFUNC_OUT_DEC_DIGIT_TEMP1 == 0# define MFUNC_OUT_DEC_STR __MFUNC_OUT_DEC_DIGIT_TEMP0# else# define MFUNC_OUT_DEC_STR CONNECT2( __MFUNC_OUT_DEC_DIGIT_TEMP1,\ __MFUNC_OUT_DEC_DIGIT_TEMP0)# endif#else# define MFUNC_OUT_DEC_STR CONNECT3( __MFUNC_OUT_DEC_DIGIT_TEMP2,\ __MFUNC_OUT_DEC_DIGIT_TEMP1,\ __MFUNC_OUT_DEC_DIGIT_TEMP0)#endif

#undef MFUNC_IN_U8_DEC_VALUE
此時(shí),保存在  MFUNC_OUT_U8_DEC_VALUE 中的值就是我們所需的十進(jìn)制數(shù)字了。為了方便使用,我們將上述內(nèi)容放置到一個(gè)專門的頭文件中,就叫做mf_u8_dec2str.h ( https://github.com/vsfteam/vsf/blob/master/source/vsf/utilities/preprocessor/mf_u8_dec2str.h),修改前面的例子:

#include "app_cfg.h"
#ifndef DEBUG_USART#   define DEBUG_USART    USART0_idx#endif
/* 建立腳本輸入值與 DEBUG_USART 之間的引用關(guān)系*/#undef MFUNC_IN_U8_DEC_VALUE#define MFUNC_IN_U8_DEC_VALUE DEBUG_USART
/* "調(diào)用"轉(zhuǎn)換腳本 */#include "mf_u8_dec2str.h"
/* 建立 DEBUG_USART 與腳本輸出值之間的引用 */#undef DEBUG_USART#define DEBUG_USART MFUNC_OUT_U8_DEC_VALUE
USART_INIT(DEBUG_USART);

打完收工。



干貨不易,如果你覺(jué)得這篇文章對(duì)你有所幫助或是有所啟發(fā),點(diǎn)贊、轉(zhuǎn)發(fā)、收藏三聯(lián)!

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
關(guān)閉
關(guān)閉