亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频

蟲蟲首頁| 資源下載| 資源專輯| 精品軟件
登錄| 注冊

您現在的位置是:首頁 > 技術閱讀 >  【為宏正名】本應寫入教科書的“世界設定”

【為宏正名】本應寫入教科書的“世界設定”

時間:2024-02-13
【說在前面的話】

市面上大部分C程序員對宏存在巨大的誤解甚至是恐懼,并因此極力避免宏的適度使用,甚至將宏在封裝中發揮正確作用的行為視作是對C語言的“背叛”——震驚之余,對于為什么大家會有這種想法的原因,我曾經一度是非常“傲慢的”,這種傲慢與某些人宣稱“窮人都是因為懶所以才窮”時所表現出的那種態度并無任何本質不同——然而我錯了,在閑暇之余認真看了不少經典的C語言教材后我才意識到:

不是讀者普遍懶或者輕視教材中有關宏的內容,而是那些對宏來說如同“加法交換律、結合律”一樣的基本規則和知識并沒有認真且完整的出現在教科書中!


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

  • 定義宏的時候,為什么遇到哪怕很基本的小問題也根本無從下手

  • 為什么那么多人聲稱系統提供的諸如 __LINE__ 之類的宏時好時壞

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


真是哭笑不得。這些規則是如此簡單,介紹一下根本無需多么復雜的篇幅。接下來,讓我們簡單的學習一下這些本應該寫入教科書中的基本內容。注意,這與你們在其它公眾號里學到的關于某些宏的基本使用方法是兩回事。


【宏不屬于C語言】


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


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


知道這一知識有什么用呢?首先,你會明白,宏本身是與C語言的其它語法毫無關聯的。宏有自己的語法,且非常簡單。在進行宏展開的時候,編譯器并不會去進行任何宏以外的C語言語法檢查、甚至根本不知道C語言語法。實際上,有大量C語言老鳥特別喜歡在其它C語言以外的文本文件里使用“宏”(其實還有條件編譯之類的),最典型的例子就是在Arm Compiler 6的scatter-script中用宏來定義一些地址常數:
#! 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之前要執行“#!” 后面的命令行,這里的"-E"就是告訴armclang:“我們只進行預編譯”——也就是"#include"以及宏替換之類的工作——所以宏“ADDRESS” 會被替換會 0x20000000,而"include_file_1.h" 中的內容也會被加入到當前的scatter-script文件中來。



需要強調下,在這個例子中,放在第一行“#!”后面的命令行之所以為會被linker自動執行,是因為linker就是這么使用 “.sct” 文件的。對于其它想使用C語言宏對任意文本文件進行預處理的場合,需要自己動手編寫命令行和腳本。比如,如果你想在 perl 里使用 C語言的預編譯,那么就需要你在執行目標 .pl 文件前,先用C語言編譯器對其進行一次預編譯。



總的來說,“宏不屬于C語言”并非空穴來風,事實上,只要你有興趣去寫腳本,包括宏在內的所有預編譯語法可以在一切文本文件中使用

知道這一知識的另外一個作用就是回答每一個C語言初學者都繞不開的經典問題:“宏和枚舉有啥區別”?有啥區別?這區別老大了:
  • 正如前面所說的,宏只存在于“預編譯階段”,而活不到“編譯階段”;宏是沒有任何C語法意義的

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


其實,從宏和枚舉服務的階段看來,他們是老死不相往來的。那么具體在使用時,這里的區別表現在什么地方呢?我們來看一個例子:

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

這里例子意圖很簡單,根據宏USART_COUNT的值來條件編譯。如果我們把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

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

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

同樣道理,如果你想借助下面的宏來生成代碼,得到的結果會出人意料:

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()

應用中,我們期望配合UARTn_idx與宏USART_INIT一起使用:

...USART_INIT(USART1_idx);...

借助宏的膠水運算“##”,我們期望的結果是:

...usart1_init();...

由于同樣的原因——在進行宏展開的時候,枚舉還沒有“出生”——實際展開的效果是這樣的:

...usartUSART1_idx_init();...

由于函數  usartUSART1_idx_init() 并不存在,所以在鏈接階段linker會報告類似“undefined symbol usartUSART1_idx_init()”——簡單說就是找不到函數。要解決這一問題也很簡單,直接把枚舉用宏來定義就可以了:


#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


那么是不是說,宏就比枚舉好呢?當然不是,準確的說法應該是:在誰的地盤誰的優點就突出。我們說枚舉僅在編譯階段有效、它具有明確的語法意義(具體語法意義請參考相應的C語言教材)。相對宏來說,怎么理解枚舉的好處呢?
  • 枚舉可以被當作類型來使用,并定義枚舉變量——宏做不到;

  • 當使用枚舉作為函數的形參或者是switch檢測的目標時,有些比較“智能”的C編譯器會在編譯階段把枚舉作為參考進行“強類型”檢測——比如檢查函數傳遞過程中你給的值是否是枚舉中實際存在的;又比如在switch中是否所有的枚舉條目都有對應的case(在缺省default的情況下)。

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

  • 少數IDE只能對枚舉進行語法提示而無法對宏進行語法提示。



【宏的本質和替換規則】

很多人都知道宏的本質是文字替換,也就是說,預編譯過程中宏會被替換成對應的字符串;然而在這一過程中所遵守的關鍵規則,很多人就不清楚了。

首先,針對一個沒有被定義過的宏:

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

  • 在#if 中被直接使用(沒有配合defined()),則很多編譯器會報告warning,指出這是一個不存在的宏,同時默認它的值是boolean量的false——而并不保證是"0";

  • 在除以上情形外的其它地方使用,比如在代碼中使用,則它會被作為代碼的一部分原樣保留到編譯階段——而不會進行任何操作;通常這會在鏈接階段觸發“undefined symbol”錯誤——這是很自然的,因為你以為你在用宏(只不過因為你忘記定義了,或者沒有正確include所需的頭文件),編譯器卻以為你在說函數或者變量——當然找不到了。


舉個例子,宏 __STDC_VERSION__ 可以被用來檢查當前ANSI-C的標準:

#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的編譯器中是不會有問題的,因為 __STDC_VERSION__ 一定會由編譯器預先定義過;而同樣的代碼放到僅支持C89/90的環境中就有可能會出問題,因為 __STDC_VERSION__ 并不保證一定會被事先定義好(C89/90并沒有規定要提供這個宏),因此 __STDC_VERSION__ 就有可能成為一個未定義的宏,從而觸發編譯器的warning。為了修正這一問題,我們需要對上述內容進行適當的修改:

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


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

  • 在#if 中被直接使用(沒有配合defined()),編譯器會把它看作“空”;在一些數值表達式中,它會被默認當作“0”,沒有任何警告信息會被產生

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


最后,我們來說一個容易被人忽視的結論:
  • 第一條:任何使用到膠水運算“##”對形參進行粘合的參數宏,一定需要額外的再套一層

  • 第二條:其余情況下,如果要用到膠水運算,一定要在內部借助參數宏來完成粘合過程


為了理解這一“結論”,我們不妨舉一個例子:在前面的代碼中,我們定義過一個用于自動關閉中斷并在完成指定操作后自動恢復原來狀態的宏:
#define SAFE_ATOM_CODE(...)               \  {                                       \      uint32_t wTemp = __disable_irq();   \      __VA_ARGS__;                        \      __set_PRIMASK(wTemp);               \  }

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

#define SAFE_ATOM_CODE(...)                           \  {                                                   \      uint32_t wTemp##__LINE__ = __disable_irq();     \      __VA_ARGS__;                                    \      __set_PRIMASK(wTemp);                           \  }
一個使用例子:
...SAFE_ATOM_CODE(    /* do something here */    ...)...
假設這里 SAFE_ATOM_CODE 所在行的行號是 123,那么我們期待的代碼展開是這個樣子的(我重新縮進過了):
...  {                                                         uint32_t wTemp123 = __disable_irq();           __VA_ARGS__;                                          __set_PRIMASK(wTemp);                             }...
然而,實際展開后的內容是這樣的:
...  {                                                         uint32_t wTemp__LINE__ = __disable_irq();           __VA_ARGS__;                                          __set_PRIMASK(wTemp);                             }...
這里,__LINE__似乎并沒有被正確替換為123,而是以原樣的形式與wTemp粘貼到了一起——這就是很多人經常抱怨的 __LINE__ 宏不穩定的問題。實際上,這是因為上述宏的構建沒有遵守前面所列舉的兩條結論導致的。

從內容上看,SAFE_ATOM_CODE() 要粘合的對象并不是形參,根據結論第二條,需要借助另外一個參數宏來幫忙完成這一過程。為此,我們需要引入一個專門的宏:

#define CONNECT2(__A, __B)    __A##__B
注意到,這個參數宏要對形參進行膠水運算,根據結論第一條,需要在宏的外面再套一層,因此,修改代碼得到:
#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);                           \  }
有興趣的朋友可以通過 "-E" 可以觀察到 __LINE__ 被正確的展開了。

【宏是引用而非變量】

具體實踐中,很多人在使用宏過程中會產生“宏是一種變量”的錯覺,這是因為無論一個宏此前是否定義過,我們都可以借助 #undef 操作,強制注銷它,從而有能力重新給這一宏賦予一個新的值,例如:
#include <stdbool.h>
#undef false#undef true
#define false     0#define true      (!false)

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


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



我們說宏的本質是一個引用,那么如何理解這種說法呢?我們來看一個例子:
#define EXAMPLE_A          123#define EXAMPLE            EXAMPLE_A
#undef  EXAMPLE_A

對于下面的代碼:

CONNECT2(uint32_t wVariable, EXAMPLE);

如果宏是一個變量,那么展開的結果應該是:

uint32_t wVariable123;

然而,我們實際獲得的是:

uint32_t wVariableEXAMPLE_A;

如何理解這一結果呢?


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

?


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

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

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

#undef __MFUNC_OUT_DEC_DIGIT_TEMP0#undef __MFUNC_OUT_DEC_DIGIT_TEMP1#undef __MFUNC_OUT_DEC_DIGIT_TEMP2#undef __MFUNC_OUT_DEC_STR_TEMP
/* 獲取個位 */#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
/* 獲取十位數字 */#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
/* 獲取百位數字 */#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
接下來,我們將代表“個、十、百”的三個宏拼接起來:
#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
此時,保存在 MFUNC_OUT_U8_DEC_VALUE 中的值就是我們所需的十進制數字了。為了方便使用,我們將上述內容放置到一個專門的頭文件中,就叫做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 之間的引用關系*/#undef MFUNC_IN_U8_DEC_VALUE#define MFUNC_IN_U8_DEC_VALUE DEBUG_USART
/* "調用"轉換腳本 */#include "mf_u8_dec2str.h"
/* 建立 DEBUG_USART 與腳本輸出值之間的引用 */#undef DEBUG_USART#define DEBUG_USART MFUNC_OUT_U8_DEC_VALUE
USART_INIT(DEBUG_USART);

打完收工。

往期推薦


1、深度好文|面試官:進程和線程,我只問這19個問題

2、他來了,他來了,C++17新特性精華都在這了

3、一文讓你搞懂設計模式

4、C++11新特性,所有知識點都在這了!




如果喜歡這篇文章,請點贊、在看,支持一下哦~謝謝!

主站蜘蛛池模板: 绥江县| 闽侯县| 临夏市| 牡丹江市| 遵义市| 延寿县| 界首市| 高安市| 新蔡县| 沾化县| 龙游县| 信宜市| 上饶市| 永泰县| 邯郸市| 峨山| 石台县| 赣州市| 资讯 | 马边| 万山特区| 贵德县| 乾安县| 扶绥县| 宁安市| 凤城市| 福海县| 乐业县| 高清| 江安县| 凤翔县| 同江市| 洪湖市| 侯马市| 左云县| 定陶县| 遵义市| 石屏县| 南通市| 泸州市| 密山市|