探索字節(jié)隊(duì)列的魔法:多類型支持、函數(shù)重載與線程安全
代碼難度指數(shù):
文章學(xué)習(xí)重點(diǎn):參數(shù)宏的使用技巧
一、引言
在嵌入式系統(tǒng)和實(shí)時應(yīng)用中,數(shù)據(jù)的傳輸和處理是至關(guān)重要的。字節(jié)隊(duì)列(Byte Queue)是一種重要的數(shù)據(jù)結(jié)構(gòu),它能夠高效地存儲和管理數(shù)據(jù)流。通過使用字節(jié)隊(duì)列,我們可以靈活地處理不同類型的數(shù)據(jù)、確保數(shù)據(jù)的完整性,并在多線程環(huán)境中安全地進(jìn)行操作。本文將深入探討字節(jié)隊(duì)列的概念、作用及其實(shí)現(xiàn)中的多類型支持、函數(shù)重載與線程安全機(jī)制。
1.1 隊(duì)列的基本概念
隊(duì)列是一種先進(jìn)先出(FIFO,F(xiàn)irst In First Out)的數(shù)據(jù)結(jié)構(gòu)。數(shù)據(jù)通過“入隊(duì)”(enqueue)操作添加到隊(duì)列的尾部,并通過“出隊(duì)”(dequeue)操作從隊(duì)列的頭部移除。在嵌入式系統(tǒng)中,隊(duì)列常用于:
- 數(shù)據(jù)緩沖:在數(shù)據(jù)產(chǎn)生和消費(fèi)速率不匹配的情況下,隊(duì)列可以暫存數(shù)據(jù),平衡輸入和輸出之間的差異。
- 任務(wù)調(diào)度:任務(wù)或事件的管理可以通過隊(duì)列來實(shí)現(xiàn),確保它們按照特定順序被處理。
- 通信:隊(duì)列可以在不同模塊或線程之間傳遞信息,從而實(shí)現(xiàn)模塊間的解耦和同步。
1.2 字節(jié)隊(duì)列的不足
盡管字節(jié)隊(duì)列在嵌入式系統(tǒng)中提供了基本的數(shù)據(jù)存儲與管理能力,但其在實(shí)際應(yīng)用中也存在一些明顯的不足:
- 缺乏多類型支持:傳統(tǒng)的字節(jié)隊(duì)列往往只能處理單一類型的數(shù)據(jù),例如,使用固定的字節(jié)數(shù)組存儲數(shù)據(jù),導(dǎo)致不同數(shù)據(jù)類型之間缺乏靈活性。為了支持不同類型的數(shù)據(jù),開發(fā)者通常需要創(chuàng)建多個隊(duì)列,從而增加了代碼的復(fù)雜性和維護(hù)成本。
- 沒有函數(shù)重載:在 C 語言中,函數(shù)重載是通過不同的函數(shù)名稱來實(shí)現(xiàn)的,缺乏類似 C++的靈活性。這使得在隊(duì)列操作中無法方便地處理不同數(shù)量和類型的參數(shù),導(dǎo)致代碼冗長且不易維護(hù)。
- 線程安全機(jī)制不足:在多線程環(huán)境中,若多個線程同時訪問字節(jié)隊(duì)列而沒有適當(dāng)?shù)耐綑C(jī)制,可能會導(dǎo)致數(shù)據(jù)損壞或不一致。傳統(tǒng)的字節(jié)隊(duì)列實(shí)現(xiàn)往往沒有內(nèi)置的線程安全支持,增加了并發(fā)編程的難度。
二、字節(jié)隊(duì)列的改進(jìn)
2.1 多類型支持的實(shí)現(xiàn)原理
問題: C 語言中的數(shù)組或緩沖區(qū)往往只能存儲單一類型的數(shù)據(jù)。例如,你可以定義一個 uint8_t 數(shù)組來存儲字節(jié)數(shù)據(jù),或者一個 int32_t 數(shù)組來存儲整型數(shù)據(jù)。然而,在嵌入式系統(tǒng)中,我們常常需要處理各種類型的數(shù)據(jù)——8 位、16 位、32 位的整數(shù)、浮點(diǎn)數(shù)等等。為了避免為每種類型單獨(dú)創(chuàng)建隊(duì)列,我們希望有一個靈活的隊(duì)列,可以自動支持多種數(shù)據(jù)類型。
解決方案: 我們使用 C 語言的宏來解決這個問題。通過宏,隊(duì)列可以自動根據(jù)傳入的數(shù)據(jù)類型來計算所需的存儲空間。核心思想是:我們不關(guān)心具體的數(shù)據(jù)類型,而是通過宏和類型推導(dǎo),計算每個數(shù)據(jù)需要的字節(jié)數(shù),并按照字節(jié)的形式將數(shù)據(jù)存入隊(duì)列中。
使用 typeof 推斷數(shù)據(jù)類型:
C 語言的 typeof 關(guān)鍵字可以根據(jù)表達(dá)式自動推斷出數(shù)據(jù)類型,并可以通過該類型確定數(shù)據(jù)的大小。在我們的實(shí)現(xiàn)中,隊(duì)列的操作宏會通過 sizeof 來獲取傳入數(shù)據(jù)的字節(jié)大小。
示例:
#defineenqueue(queue,data)enqueue_bytes(queue,&data,sizeof(typeof(data)))
在上述宏中:
- typeof(data) 會推斷出 data 的類型,然后通過 sizeof(typeof(data))確定該類型占用的字節(jié)數(shù)。
- 通過將數(shù)據(jù)的地址傳遞給底層的 enqueue_bytes 函數(shù),我們可以統(tǒng)一將所有類型的數(shù)據(jù)作為字節(jié)流處理。
通過這種方式,我們的隊(duì)列可以支持任意類型的數(shù)據(jù),比如 8 位字節(jié)、16 位整數(shù)、32 位浮點(diǎn)數(shù),甚至自定義的數(shù)據(jù)結(jié)構(gòu),只要知道它們的大小即可。
2.2 函數(shù)重載的實(shí)現(xiàn)原理
問題: 在 C++等語言中,函數(shù)重載允許你定義多個同名的函數(shù),但參數(shù)類型或數(shù)量不同。然而,C 語言并不原生支持函數(shù)重載。這意味著如果我們想實(shí)現(xiàn)同名函數(shù),處理不同類型或數(shù)量的參數(shù),就需要想出另一種方法。
解決方案: 我們可以通過 C 語言的宏來“模擬”函數(shù)重載。宏的靈活性使得我們可以根據(jù)不同的參數(shù)數(shù)量或類型,選擇不同的底層函數(shù)進(jìn)行處理。結(jié)合__VAARGS_等可變參數(shù)宏的特性,我們可以輕松實(shí)現(xiàn)這種重載行為。
使用宏實(shí)現(xiàn)參數(shù)數(shù)量的重載: enqueue 可以根據(jù)傳遞的參數(shù)數(shù)量,調(diào)用不同的函數(shù)。我們使用__VAARGS_(可變參數(shù))來處理不同數(shù)量的參數(shù)。
enqueue 宏的完整代碼如下:
#define__CONNECT3(__A,__B,__C)__A##__B##__C
#define__CONNECT2(__A,__B)__A##__B
#defineCONNECT3(__A,__B,__C)__CONNECT3(__A,__B,__C)
#defineCONNECT2(__A,__B)__CONNECT2(__A,__B)
#defineSAFE_NAME(__NAME)CONNECT3(__,__NAME,__LINE__)
#define__PLOOC_VA_NUM_ARGS_IMPL(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,\
_12,_13,_14,_15,_16,__N,...)__N
#define__PLOOC_VA_NUM_ARGS(...)\
__PLOOC_VA_NUM_ARGS_IMPL(0,##__VA_ARGS__,16,15,14,13,12,11,10,9,\
8,7,6,5,4,3,2,1,0)
#define__ENQUEUE_0(__QUEUE,__VALUE)\
({typeof((__VALUE))SAFE_NAME(value)=__VALUE;\
enqueue_bytes((__QUEUE),&(SAFE_NAME(value)),(sizeof(__VALUE)));})
#define__ENQUEUE_1(__QUEUE,__ADDR,__ITEM_COUNT)\
enqueue_bytes((__QUEUE),(__ADDR),__ITEM_COUNT*(sizeof(typeof((__ADDR[0])))))
#define__ENQUEUE_2(__QUEUE,__ADDR,__TYPE,__ITEM_COUNT)\
enqueue_bytes((__QUEUE),(__ADDR),(__ITEM_COUNT*sizeof(__TYPE)))
#defineenqueue(__queue,__addr,...)\
CONNECT2(__ENQUEUE_,__PLOOC_VA_NUM_ARGS(__VA_ARGS__))\
(__queue,(__addr),##__VA_ARGS__)
工作原理:
通過以上代碼,enqueue宏會根據(jù)傳遞的參數(shù)數(shù)量,自動選擇不同的實(shí)現(xiàn)版本
- 傳遞的可變參數(shù)如果為 0,調(diào)用__ENQUEUE_0;
- 傳遞的可變參數(shù)如果為 1,調(diào)用__ENQUEUE_1;
- 傳遞的可變參數(shù)如果為 2,調(diào)用__ENQUEUE_2。
2.2.1 函數(shù)重載的秘密 ——“__PLOOC_VA_NUM_ARGS”宏的深度剖析
__PLOOC_VA_NUM_ARGS 宏的代碼如下:
#define__PLOOC_VA_NUM_ARGS_IMPL(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,\
_13,_14,_15,_16,__N,...)__N
#define__PLOOC_VA_NUM_ARGS(...)\
__PLOOC_VA_NUM_ARGS_IMPL(0,##__VA_ARGS__,16,15,14,13,12,11,10,9,\
8,7,6,5,4,3,2,1,0)
- __PLOOC_VA_NUM_ARGS 宏的作用是它可以告訴我們實(shí)際傳遞了多少個參數(shù)
這里,首先構(gòu)造了一個特殊的參數(shù)宏,__PLOOC_VA_NUM_ARGS_IMPL():
- 在涉及"..."之前,它要用戶至少傳遞 18 個參數(shù);
- 這個宏的返回值就是第十八個參數(shù)的內(nèi)容;
- 多出來的部分會被"..."吸收掉,不會產(chǎn)生任何后果
__PLOOC_VA_NUM_ARGS 的巧妙在于,它把__VA_ARGS__放在了參數(shù)列表的最前面,并隨后傳遞了 "16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0" 這樣的序號:
當(dāng)__VA_ARGS__里有 1 個參數(shù)時,“1”對應(yīng)第十八個參數(shù)__N,所以返回值是 1
當(dāng)__VA_ARGS__里有 2 個參數(shù)時,“2”對應(yīng)第十八個參數(shù)__N,所以返回值是 2
...
當(dāng)__VA_ARGS__里有 9 個參數(shù)時,"9"對應(yīng)第十八個參數(shù)__N,所以返回值是 9
舉個例子:
__PLOOC_VA_NUM_ARGS(0x,D,E,A,D)
展開為:
__PLOOC_VA_NUM_ARGS_IMPL(0,0x,D,E,A,D,16,15,14,13,12,11,10,9,\
8,7,6,5,4,3,2,1,0)
__PLOOC_VA_NUM_ARGS 的返回值是 5,從左往右數(shù),第十八個參數(shù),正好是“5”。
- 宏連接符##的作用
#define__CONNECT2(__A,__B)__A##__B
#defineCONNECT2(__A,__B)__CONNECT2(__A,__B)
宏連接符 ## 的主要作用就是連接兩個字符串,我們在宏定義中可以使用 ## 來連接兩個字符。預(yù)處理器在預(yù)處理階段對宏展開時,會將## 兩邊的字符合并,并刪除 ## 這兩個字符。
使用宏連接符 ##要注意一下兩條結(jié)論:
- 第一條:任何使用到膠水運(yùn)算“##”對形參進(jìn)行粘合的參數(shù)宏,一定需要額外的再套一層
- 第二條:其余情況下,如果要用到膠水運(yùn)算,一定要在內(nèi)部借助參數(shù)宏來完成粘合過程
為了理解這一“結(jié)論”,我們不妨舉一個例子:比如定義一個用于自動關(guān)閉中斷并在完成指定操作后自動恢復(fù)原來狀態(tài)的宏:
#defineSAFE_ATOM_CODE(...)\
{\
uint32_twTemp=__disable_irq();\
__VA_ARGS__;\
__set_PRIMASK(wTemp);\
}
由于這里定義了一個變量 wTemp,而如果用戶插入的代碼中也使用了同名的變量,就會產(chǎn)生很多問題:輕則編譯錯誤(重復(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)等等)。為了避免這一問題,我們往往會想自動給這個變量一個不會重復(fù)的名字,比如借助 __LINE__ 宏給這一變量加入一個后綴:
#defineSAFE_ATOM_CODE(...)\
{\
uint32_twTemp##__LINE__=__disable_irq();\
__VA_ARGS__;\
__set_PRIMASK(wTemp);\
}
假設(shè)這里 SAFE_ATOM_CODE 所在行的行號是 123,那么我們期待的代碼展開是這個樣子的(我重新縮進(jìn)過了):
...
{
uint32_twTemp123=__disable_irq();
__VA_ARGS__;
__set_PRIMASK(wTemp);
}
...
然而,實(shí)際展開后的內(nèi)容是這樣的:
...
{
uint32_twTemp__LINE__=__disable_irq();
__VA_ARGS__;
__set_PRIMASK(wTemp);
}
...
這里,__LINE__似乎并沒有被正確替換為 123,而是以原樣的形式與 wTemp 粘貼到了一起——這就是很多人經(jīng)常抱怨的 __LINE__ 宏不穩(wěn)定的問題。實(shí)際上,這是因?yàn)樯鲜龊甑臉?gòu)建沒有遵守前面所列舉的兩條結(jié)論導(dǎo)致的。
從內(nèi)容上看,SAFE_ATOM_CODE() 要粘合的對象并不是形參,根據(jù)結(jié)論第二條,需要借助另外一個參數(shù)宏來幫忙完成這一過程。為此,我們需要引入一個專門的宏:
#defineCONNECT2(__A,__B)__A##__B
注意到,這個參數(shù)宏要對形參進(jìn)行膠水運(yùn)算,根據(jù)結(jié)論第一條,需要在宏的外面再套一層,因此,修改代碼得到:
#define__CONNECT2(__A,__B)__A##__B
#defineCONNECT2(__A,__B)__CONNECT2(__A,__B)
修改前面的定義得到:
#defineSAFE_ATOM_CODE(...)\
{\
uint32_tCONNECT2(wTemp,__LINE__)=\
__disable_irq();\
__VA_ARGS__;\
__set_PRIMASK(wTemp);\
}
- 對 enqueue 的封裝進(jìn)行展開
#defineenqueue(__queue,__addr,...)\
CONNECT2(__ENQUEUE_,__PLOOC_VA_NUM_ARGS(__VA_ARGS__))\
(__queue,(__addr),##__VA_ARGS__)
CONNECT2 會根據(jù)__PLOOC_VA_NUM_ARGS 返回的數(shù)量,與__ENQUEUE_進(jìn)行連接,
- __PLOOC_VA_NUM_ARGS 返回的數(shù)量如果為 0,調(diào)用__ENQUEUE_0(__queue,(__addr),##__VA_ARGS__);
- __PLOOC_VA_NUM_ARGS 返回的數(shù)量如果為 1,調(diào)用__ENQUEUE_1(__queue,(__addr),##__VA_ARGS__);
- __PLOOC_VA_NUM_ARGS 返回的數(shù)量如果為 2,調(diào)用__ENQUEUE_2(__queue,(__addr),##__VA_ARGS__);
舉個例子:
staticbyte_queue_tmy_queue;
uint8_tdata1=0XAA;
enqueue(&my_queue,data1);//__ENQUEUE_0(&my_queue,data1)
enqueue(&my_queue,&data1,1);//__ENQUEUE_1(&my_queue,&data1,1)
enqueue(&my_queue,&data1,uint8_t,1);//__ENQUEUE_2(&my_queue,&data1,uint8_t,1)
/*__ENQUEUE_0,__ENQUEUE_1,__ENQUEUE_2,展開后調(diào)用的都是同一個接口*/
enqueue_bytes(&my_queue,&data1,1)
2.3 線程安全的實(shí)現(xiàn)原理
問題: 在多線程環(huán)境下,如果多個線程同時對同一個隊(duì)列進(jìn)行操作,可能會引發(fā)數(shù)據(jù)競爭問題,導(dǎo)致數(shù)據(jù)損壞或不一致。為了避免這種情況,我們需要保證每次對隊(duì)列的操作是原子的,即不可打斷的。
解決方案: 在嵌入式系統(tǒng)中,常用的方法是通過禁用中斷或使用鎖機(jī)制來保證數(shù)據(jù)的一致性。在我們的實(shí)現(xiàn)中,我們使用禁用中斷的方式來確保線程安全。這是一種非常常見的技術(shù),尤其是在實(shí)時系統(tǒng)中。
為了盡量降低關(guān)中斷對實(shí)時性的影響,我們只對操作隊(duì)列指針的操作進(jìn)行關(guān)中斷保護(hù),相對耗時間的數(shù)據(jù)拷貝不進(jìn)行關(guān)中斷。
函數(shù)偽代碼如下:
boolenqueue_bytes(...)
{
boolbEarlyReturn=false;
safe_atom_code(){
if(!this.bMutex){
this.bMutex=true;
}else{
bEarlyReturn=true;
}
}
if(bEarlyReturn){
returnfalse;
}
safe_atom_code(){
/*隊(duì)列指針操作*/
...
}
/*數(shù)據(jù)操作*/
memcpy(...);
...
this.bMutex=false;
returntrue;
}
原子宏 safe_atom_code()的實(shí)現(xiàn):
前邊的例子中,我們實(shí)現(xiàn)了一個 SAFE_ATOM_CODE 的原子宏,唯一的問題是,這樣的寫法,在調(diào)試時完全沒法在用戶代碼處添加斷點(diǎn)(編譯器會認(rèn)為宏內(nèi)所有的內(nèi)容都寫在了同一行),這是大多數(shù)人不喜歡使用宏來封裝代碼結(jié)構(gòu)的最大原因。
接下來我們用另一種實(shí)現(xiàn)方式來解決這個問題,代碼如下:
#define__CONNECT3(__A,__B,__C)__A##__B##__C
#define__CONNECT2(__A,__B)__A##__B
#defineCONNECT3(__A,__B,__C)__CONNECT3(__A,__B,__C)
#defineCONNECT2(__A,__B)__CONNECT2(__A,__B)
#defineSAFE_NAME(__NAME)CONNECT3(__,__NAME,__LINE__)
#include"cmsis_compiler.h"
#definesafe_atom_code()\
for(uint32_tSAFE_NAME(temp)=\
({uint32_tSAFE_NAME(temp2)=__get_PRIMASK();\
__disable_irq();\
SAFE_NAME(temp2);}),*SAFE_NAME(temp3)=NULL;\
SAFE_NAME(temp3)++==NULL;\
__set_PRIMASK(SAFE_NAME(temp)))
#endif
工作原理:
safe_atom_code()通過一個循環(huán)結(jié)構(gòu)確保在隊(duì)列操作期間,中斷被禁用。循環(huán)結(jié)束后自動恢復(fù)中斷。
2.3.1 for 循環(huán)的妙用
首先構(gòu)造一個只執(zhí)行一次的 for 循環(huán)結(jié)構(gòu):
for(inti=1;i>0;i--){
...
}
對于這樣的 for 循環(huán)結(jié)構(gòu),幾個關(guān)鍵部分就有了新的意義:
- 在執(zhí)行用戶代碼之前(灰色部分),有能力進(jìn)行一定的“準(zhǔn)備工作”(Before 部分);
- 在執(zhí)行用戶代碼之后,有能力執(zhí)行一定的“收尾工作”(After 部分);
- 在 init_clause 階段有能力定義一個“僅僅只覆蓋” for 循環(huán)的,并且只對 User Code 可見的局部變量——換句話說,這些局部變量是不會污染 for 循環(huán)以外的地方的。
利用這樣的結(jié)構(gòu),我們很容易就能構(gòu)造出一個可以通過花括號的形式來包裹用戶代碼的原子操作 safe_atom_code(),在執(zhí)行用戶代碼之前關(guān)閉中斷,在執(zhí)行完用戶代碼之后打開中斷,還不影響在用戶代碼中添加斷點(diǎn),單步執(zhí)行。
需要注意的是,如果需要中途退出循環(huán),需要使用continue退出原子操作,不能使用break。
2.4 總結(jié)
通過上述的多類型支持、函數(shù)重載和線程安全的實(shí)現(xiàn),我們大大增強(qiáng)了字節(jié)隊(duì)列的靈活性和實(shí)用性:
- 多類型支持: 自動推斷數(shù)據(jù)類型和大小,支持不同類型數(shù)據(jù)的隊(duì)列操作。
- 函數(shù)重載: 通過宏模擬 C 語言的函數(shù)重載,靈活處理不同數(shù)量和類型的參數(shù)。
- 線程安全: 通過禁用中斷機(jī)制確保隊(duì)列操作在多線程環(huán)境中的原子性,避免數(shù)據(jù)競爭問題。
這些改進(jìn)使得我們的字節(jié)隊(duì)列不僅可以在單線程環(huán)境中高效運(yùn)行,還能在復(fù)雜的多線程系統(tǒng)中保持?jǐn)?shù)據(jù)的一致性與安全性。
三、API 接口
#definequeue_init(__queue,__buffer,__size,...)\
__PLOOC_EVAL(__QUEUE_INIT_,##__VA_ARGS__)\
(__queue,(__buffer),(__size),##__VA_ARGS__)
#definedequeue(__queue,__addr,...)\
__PLOOC_EVAL(__DEQUEUE_,##__VA_ARGS__)\
(__queue,(__addr),##__VA_ARGS__)
#defineenqueue(__queue,__addr,...)\
__PLOOC_EVAL(__ENQUEUE_,##__VA_ARGS__)\
(__queue,(__addr),##__VA_ARGS__)
#definepeek_queue(__queue,__addr,...)\
__PLOOC_EVAL(__PEEK_QUEUE_,##__VA_ARGS__)\
(__queue,(__addr),##__VA_ARGS__)
extern
byte_queue_t*queue_init_byte(byte_queue_t*ptObj,void*pBuffer,uint16_thwItemSize,boolbIsCover);
extern
boolreset_queue(byte_queue_t*ptObj);
extern
uint16_tenqueue_bytes(byte_queue_t*ptObj,void*pDate,uint16_thwDataLength);
extern
uint16_tdequeue_bytes(byte_queue_t*ptObj,void*pDate,uint16_thwDataLength);
extern
boolis_queue_empty(byte_queue_t*ptQueue);
extern
boolis_peek_empty(byte_queue_t*ptObj);
extern
uint16_tpeek_bytes_queue(byte_queue_t*ptObj,void*pDate,uint16_thwDataLength);
extern
voidreset_peek(byte_queue_t*ptQueue);
extern
voidget_all_peeked(byte_queue_t*ptQueue);
extern
uint16_tget_peek_status(byte_queue_t*ptQueue);
extern
voidrestore_peek_status(byte_queue_t*ptQueue,uint16_thwCount);
extern
uint16_tget_queue_count(byte_queue_t*ptObj);
extern
uint16_tget_queue_available_count(byte_queue_t*ptObj);
四、API 說明
- 初始化隊(duì)列
queue_init(__queue,__buffer,__size,...)
參數(shù)說明:
參數(shù)名 | 描述 |
---|---|
__QUEUE | 隊(duì)列的地址 |
__BUFFER | 隊(duì)列緩存的首地址 |
__BUFFER_SIZE | 隊(duì)列長度 |
可變參數(shù) | 是否覆蓋,默認(rèn)否 |
- 入隊(duì)
#defineenqueue(__queue,__addr,...)
參數(shù)說明:
參數(shù)名 | 描述 |
---|---|
__QUEUE | 隊(duì)列的地址 |
__ADDR | 待入隊(duì)的數(shù)據(jù)或者數(shù)據(jù)的地址 |
... | 可變參數(shù),需要入隊(duì)的數(shù)據(jù)個數(shù),或者數(shù)據(jù)類型和個數(shù),如果為空,則只入隊(duì)一個數(shù)據(jù) |
- 出隊(duì)
#definedequeue(__queue,__addr,...)
參數(shù)說明:
參數(shù)名 | 描述 |
---|---|
__QUEUE | 隊(duì)列的地址 |
__ADDR | 用于保存出隊(duì)數(shù)據(jù)變量的地址 |
... | 可變參數(shù),需要出隊(duì)的數(shù)據(jù)個數(shù),或者數(shù)據(jù)類型和個數(shù),如果為空,則只出隊(duì)一個數(shù)據(jù) |
- 查看
#definepeek_queue(__queue,__addr,...)
參數(shù)說明:
參數(shù)名 | 描述 |
---|---|
__QUEUE | 隊(duì)列的地址 |
__ADDR | 用于保存查看數(shù)據(jù)變量的地址 |
... | 可變參數(shù),數(shù)據(jù)類型和需要查看的數(shù)據(jù)個數(shù),如果為空,則只查看一個數(shù)據(jù) |
五、快速使用
代碼開源地址:https://github.com/Aladdin-Wang/wl_queue
或者打開MicroBoot,介紹鏈接:徹底解決單片機(jī)BootLoader升級程序失敗問題,只勾選queue,如圖所示:
使用實(shí)例:
#include"ring_queue.h"
uint8_tdata1=0XAA;
uint16_tdata2=0X55AA;
uint32_tdata3=0X55AAAA55;
uint16_tdata4[]={0x1234,0x5678};
typedefstructdata_t{
uint32_ta;
uint32_tb;
uint32_tc;
}data_t;
data_tdata5={
.a=0X11223344,
.b=0X55667788,
.c=0X99AABBCC,
};
uint8_tdata[100];
staticuint8_ts_hwQueueBuffer[100];
staticbyte_queue_tmy_queue;
queue_init(&my_queue,s_hwQueueBuffer,sizeof(s_hwQueueBuffer));
//根據(jù)變量的類型,自動計算對象的大小
enqueue(&my_queue,data1);
enqueue(&my_queue,data2);
enqueue(&my_queue,data3);
//一下三種方式都可以正確存儲數(shù)組
enqueue(&my_queue,data4,2);//可以不指名數(shù)據(jù)類型
enqueue(&my_queue,data4,uint16_t,2);//也可以指名數(shù)據(jù)類型
enqueue(&my_queue,data4,uint8_t,sizeof(data4));//或者用其他類型
//一下兩種方式都可以正確存儲結(jié)構(gòu)體類型
enqueue(&my_queue,data5);//根據(jù)結(jié)構(gòu)體的類型,自動計算對象的大小
enqueue(&my_queue,&data5,uint8_t,sizeof(data5));//也可以以數(shù)組方式存儲
enqueue(&my_queue,(uint8_t)0X11);//常量默認(rèn)為int型,需要強(qiáng)制轉(zhuǎn)換數(shù)據(jù)類型
enqueue(&my_queue,(uint16_t)0X2233);//常量默認(rèn)為int型,需要強(qiáng)制轉(zhuǎn)換數(shù)據(jù)類型
enqueue(&my_queue,0X44556677);
enqueue(&my_queue,(char)'a');//單個字符也需要強(qiáng)制轉(zhuǎn)換數(shù)據(jù)類型
enqueue(&my_queue,"bc");//字符串默認(rèn)會存儲空字符\0
enqueue(&my_queue,"def");
//讀出全部數(shù)據(jù)
dequeue(&my_queue,data,get_queue_count(&my_queue));
結(jié)語
本文的目的,告訴大家如何正確的看待宏——宏不是阻礙代碼開發(fā)和可讀性的魔鬼:
宏不是奇技淫巧
宏可以封裝出其它高級語言所提供的“基礎(chǔ)設(shè)施”
設(shè)計良好的宏可以提升代碼的可讀性,而不是破壞它
設(shè)計良好的宏并不會影響調(diào)試
宏可以用來固化某些模板,避免每次都重新編寫復(fù)雜的語法結(jié)構(gòu)
-
嵌入式系統(tǒng)
+關(guān)注
關(guān)注
41文章
3593瀏覽量
129466 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4331瀏覽量
62606 -
數(shù)據(jù)結(jié)構(gòu)
+關(guān)注
關(guān)注
3文章
573瀏覽量
40130
發(fā)布評論請先 登錄
相關(guān)推薦
評論