[英]C Macros that invoke different Macros according to the argument values
我正在嘗試在 C2000 微控制器上實現寄存器訪問宏。 遺憾的是,這個微控制器沒有 8 位類型,所以當我需要訪問一個字節時,我需要調用一個宏__byte(temp,0) = 0xCA
我的一些寄存器是字節,另一些是 16 位字。 但是,我想使用相同的宏來訪問數據。
#define REG_WIDGET_ADDR 0x1000
#define REG_WIDGET_IS_BYTE 1
#define REG_GADGET_ADDR 0x1002
#define REG_GADGET_IS_BYTE 0
我想像這樣訪問寄存器。
REG_ACCESS(WIDGET) = 0x0A;
REG_ACCESS(GADGET) = 0xCAFE;
這將被轉換為不同的宏,如
REG_ACCESS2(REG_WIDGET_ADDR, REG_WIDGET_IS_BYTE) = 0x0A
REG_ACCESS2(REG_GADGET_ADDR, REG_GADGET_IS_BYTE) = 0xCAFE
然后當寄存器是一個字節時,應該將 REG_ACCESS2 宏轉換為__byte(*(0x1000),0) = 0xA
之類的東西。 當寄存器是 16 位字時,宏應轉換為類似(*(0x1002) = 0xCAFE
我知道如何創建當 arguments 的數量變化時調用不同宏的宏,但不知道如何在參數值變化時調用不同的宏。 我應該如何進行?
此代碼執行您似乎要求的操作:
#define REG_WIDGET_ADDR 0x1000
#define REG_WIDGET_IS_BYTE 1
#define REG_GADGET_ADDR 0x1002
#define REG_GADGET_IS_BYTE 0
// These two macros provide the requested expansions for word and byte access.
#define REG_ACCESS_T0(a) *(a)
#define REG_ACCESS_T1(a) __byte(*(a), 0)
// This macro uses argument b to select between the two macros above.
#define REG_ACCESS3(a, b) REG_ACCESS_T##b(a)
// This macro is needed to let argument b be replaced.
#define REG_ACCESS2(a, b) REG_ACCESS3(a, b)
// This macro uses x to get the corresponding address and is-byte macros.
#define REG_ACCESS(x) REG_ACCESS2(REG_##x##_ADDR, REG_##x##_IS_BYTE)
REG_ACCESS(WIDGET) = 0x0A;
REG_ACCESS(GADGET) = 0xCAFE;
宏替換的結果是:
__byte(*(0x1000), 0) = 0x0A;
*(0x1002) = 0xCAFE;
這個預處理問題的經典方法包括從片段中粘貼標記,然后匹配某些定義的標記。 令牌碎片的問題在於它不利於您的基本開發環境工具,例如“查找使用此標識符的位置”。
相反,我們可以采用將寄存器定義為數據框的方法,如下所示:
#define WIDGET 0x1000, BYTE
#define GADGET 0x1002, WORD
但是:我們不要完全采用這種未封裝的方法。 讓我們為這個“數據類型”創建一個“構造函數”並使用它。 原因是:如果我們在寄存器中添加了另一個屬性,我們可以在類似構造函數的宏中添加一個參數,然后編譯器會找到所有需要參數的地方,所以我們不會忘記任何地方:
#define REG(ADDR, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD
#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)
另一個優點是“屬性”現在有了名稱。 在我們的編程編輯器或 IDE 中,我們可以跳轉到REG
的定義,看看0x1000
在WIDGET
的定義中是什么意思,看到它對應一個名為ADDR
的參數。
所以現在,定義了這個“數據結構”,我們可以編寫訪問器:
#define REG(ADDR, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD
// retrieve addr
#define ADDR(ADDR, BYTE_OR_WORD) ADDR
// retrieve BYTE or WORD
#define BYTE_OR_WORD(ADDR, BYTE_OR_WORD) BYTE_OR_WORD
那么我們可以編寫訪問宏,如下所示:
#define REG_ACCESS(REG) BYTE_OR_WORD(REG)(ADDR(REG))
所以例如
REG_ACCESS(WIDGET)
擴展到
BYTE(0x1000)
現在我們只需要定義BYTE()
和WORD()
來進行訪問。 這是一個文件中的完整圖片:
// REG type constructor
#define REG(ADDRESS, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD
// Retrieve address field of type by destructuring and selecting
#define ADDR(ADDR, BOW) ADDR
// Retrieve BYTE-or-WORD field likewise
#define BYTE_OR_WORD(ADDR, BOW) BOW
// Access macro: retrieves the fields and builds expression
#define REG_ACCESS(REG) BYTE_OR_WORD(REG)(ADDR(REG))
// construct instances of various registers
#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)
// Test access
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)
使用gcc -E
進行測試:
$ gcc -E regaccess.c
# 1 "regaccess.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "regaccess.c"
# 18 "regaccess.c"
BYTE(0x1000)
WORD(0x1002)
請注意,在解決方案中,每個標識符在某處都有一個定義,並且對該標識符的所有引用都使用該標識符:沒有標識符是從不可搜索的部分粘貼在一起的。
如果我們在代碼中看到REG_ACCESS(WIDGET)
,我們可以跳轉到REG_ACCESS
的定義或WIDGET
的定義。
此外,代碼只使用了 ISO C 90 預處理器特性,但與函數式編程有一定的關系:元組的構造、解構。 這是因為預處理器被(或可以)視為純術語重寫系統。
如果我們正在做一些簡單的事情,我們不需要帶有訪問器的抽象層。 因為它們只在單個宏REG_ACCESS
中使用,我們可以讓它們消失:
// REG type constructor
#define REG(ADDRESS, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD
// Access macro's implementation
#define X_REG_ACCESS(ADDR, BOW) BOW(ADDR)
// Access macro's interface
#define REG_ACCESS(REG) X_REG_ACCESS(REG)
// construct instances of various registers
#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)
// Test access
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)
X_REG_ACCESS
解構了類型的兩部分, ADDR
和BOW
,並且不使用任何抽象訪問器,做它需要的事情:構造類型的字節或字 function 名稱到類型地址的應用程序。
它的核心是簡單本身: BOW(ADDR)
。 將ADDR
傳遞給BYTE
或WORD
。
但是,哲學問題:我們為什么不這樣做:
#define WIDGET BYTE(0x1000)
#define GADGET WORD(0x1001)
// nothing to do: WIDGET and GADGET are already access expressions:
#define REG_ACCESS(REG) REG
我們如何為早先的措辭辯護呢? 一個原因是早期的方法允許我們對有關寄存器的信息進行宏觀時間訪問,而不僅僅是生成訪問該寄存器的代碼。 如果我們只想要它的地址怎么辦? 或者是BYTE
還是WORD
。
我們的系統是可擴展的。 假設我們也想要寄存器名稱,以及一個 Boolean 1
或0
值謂詞是否寄存器是字節數據類型。 復雜的例子:
// Machine type abstraction byte or word
#define DATA_TYPE(IS_BYTE, ACCESS_FN) IS_BYTE, ACCESS_FN
// Register abstraction
#define REG(ADDRESS, DATA_TYPE, NAME) ADDRESS, DATA_TYPE, NAME
#define X_REG_ACCESS(ADDR, IS_BYTE, ACCESS_FN, NAME) ACCESS_FN(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS(REG)
#define X_REG_ADDR(ADDR, IS_BYTE, ACCESS_FN, NAME) ADDR
#define REG_ADDR(REG) X_ADDR(REG)
#define X_REG_NAME(ADDR, IS_BYTE, ACCESS_FN, NAME) NAME
#define REG_NAME(REG) X_REG_NAME(REG)
#define X_REG_IS_BYTE(ADDR, IS_BYTE, ACCESS_FN, NAME) IS_BYTE
#define REG_IS_BYTE(REG) X_REG_IS_BYTE(REG)
// Database of machine types
#define BYTE DATA_TYPE(1, get_byte)
#define WORD DATA_TYPE(0, get_word)
#define WIDGET REG(0x1000, BYTE, "I2C Acme Widget")
#define GADGET REG(0x1002, WORD, "SPI Mikro Gadget")
// Debug print about register
#define PRINT_REG_INFO(REG) \
printf("register " X_REG_NAME(REG) ", @%x, is byte: %s", \
X_REG_ADDR(REG), X_REG_IS_BYTE(REG) ? "yes" : "no")
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)
PRINT_REG_INFO(WIDGET);
PRINT_REG_INFO(GADGET);
Output:
get_byte(0x1000)
get_word(0x1002)
printf("register " "I2C Acme Widget" ", @%x, is byte: %s", 0x1000, 1 ? "yes" : "no");
printf("register " "SPI Mikro Gadget" ", @%x, is byte: %s", 0x1002, 0 ? "yes" : "no");
請注意,在第 3 部分中,此處的DATA_TYPE
是如何拼接到具有四個元素的REG
類型中的。 也就是說, REG
訪問器處理的是展開數據類型字段的平面結構。 這可能是不可取的。 如果我們想向DATA_TYPE
添加一個字段,我們必須編輯包括它的所有其他類型,以將 arguments 添加到它們的函數中。
解決方法是讓我們的構造函數添加括號,如下所示:
#define REG(ADDRESS, DATA_TYPE, NAME) (ADDRESS, DATA_TYPE, NAME)
然后在不帶括號的情況下調用X_
擴展宏:
#define X_REG_ACCESS(ADDR, DATA_TYPE, NAME) ACCESS_FN(DATA_TYPE)(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS REG
請注意X_REG_ACCESS
擴展如何從ACCESS_FN(ADDR)
更改為ACCESS_FN(DATA_TYPE)(ADDR)
: DATA_TYPE
現在已封裝,我們必須使用它的訪問器來獲取訪問 function 名稱。
整個第 3 部分解決方案重新設計: REG
的東西又是一個 3 元組,其中兩個元素DATA_TYPE
是一個元素:
// Machine type abstraction byte or word
#define DATA_TYPE(IS_BYTE, ACCESS_FN) (IS_BYTE, ACCESS_FN)
#define X_IS_BYTE(IS_BYTE, ACCESS_FN) IS_BYTE
#define IS_BYTE(DATA_TYPE) X_IS_BYTE DATA_TYPE
#define X_ACCESS_FN(IS_BYTE, ACCESS_FN) ACCESS_FN
#define ACCESS_FN(DATA_TYPE) X_ACCESS_FN DATA_TYPE
// Register abstraction
#define REG(ADDRESS, DATA_TYPE, NAME) (ADDRESS, DATA_TYPE, NAME)
#define X_REG_ACCESS(ADDR, DATA_TYPE, NAME) ACCESS_FN(DATA_TYPE)(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS REG
#define X_REG_ADDR(ADDR, DATA_TYPE, NAME) ADDR
#define REG_ADDR(REG) X_ADDR REG
#define X_REG_NAME(ADDR, DATA_TYPE, NAME) NAME
#define REG_NAME(REG) X_REG_NAME REG
#define X_REG_IS_BYTE(ADDR, DATA_TYPE, NAME) IS_BYTE(DATA_TYPE)
#define REG_IS_BYTE(REG) X_REG_IS_BYTE REG
// Database of machine types
#define BYTE DATA_TYPE(1, get_byte)
#define WORD DATA_TYPE(0, get_word)
#define WIDGET REG(0x1000, BYTE, "I2C Acme Widget")
#define GADGET REG(0x1002, WORD, "SPI Mikro Gadget")
// Debug print about register
#define PRINT_REG_INFO(REG) \
printf("register " X_REG_NAME REG ", @%x, is byte: %s", \
X_REG_ADDR REG, X_REG_IS_BYTE REG ? "yes" : "no")
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)
PRINT_REG_INFO(WIDGET);
PRINT_REG_INFO(GADGET);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.