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

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

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

探索字節(jié)隊(duì)列的魔法:多類型支持、函數(shù)重載與線程安全

RT-Thread官方賬號 ? 2024-11-15 01:08 ? 次閱讀


探索字節(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)

  1. __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”。

  1. 宏連接符##的作用

#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);\
}

  1. 對 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)以外的地方的。

0f2f2148-a2ab-11ef-8084-92fbcf53809c.png

利用這樣的結(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í)用性:

  1. 多類型支持: 自動推斷數(shù)據(jù)類型和大小,支持不同類型數(shù)據(jù)的隊(duì)列操作。
  2. 函數(shù)重載: 通過宏模擬 C 語言的函數(shù)重載,靈活處理不同數(shù)量和類型的參數(shù)。
  3. 線程安全: 通過禁用中斷機(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 說明

  1. 初始化隊(duì)列

queue_init(__queue,__buffer,__size,...)

參數(shù)說明:

參數(shù)名描述
__QUEUE隊(duì)列的地址
__BUFFER隊(duì)列緩存的首地址
__BUFFER_SIZE隊(duì)列長度
可變參數(shù)是否覆蓋,默認(rèn)否
  1. 入隊(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ù)
  1. 出隊(duì)

#definedequeue(__queue,__addr,...)

參數(shù)說明:

參數(shù)名描述
__QUEUE隊(duì)列的地址
__ADDR用于保存出隊(duì)數(shù)據(jù)變量的地址
...可變參數(shù),需要出隊(duì)的數(shù)據(jù)個數(shù),或者數(shù)據(jù)類型和個數(shù),如果為空,則只出隊(duì)一個數(shù)據(jù)
  1. 查看

#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,如圖所示:

0f3a7462-a2ab-11ef-8084-92fbcf53809c.png

使用實(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)

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

    評論

    相關(guān)推薦

    C++重載運(yùn)算符和重載函數(shù)詳解

    在計算機(jī)程序設(shè)計中,運(yùn)算符重載(英語:operator overloading)是多態(tài)的一種。這里,運(yùn)算符(比如+,=或==)被當(dāng)作多態(tài)函數(shù),它們的行為隨著其參數(shù)類型的不同而不同。運(yùn)算符并不一定總是符號。
    發(fā)表于 09-20 17:14 ?1977次閱讀

    線程編程之二 MFC中的線開發(fā)

    線程編程之二 MFC中的線開發(fā)五、MFC對多線程編程的支持  MFC中有兩類線程,分別稱之為工作者
    發(fā)表于 10-22 11:42

    關(guān)于C++中的函數(shù)重載機(jī)制

    重載機(jī)制是一種"假的"多態(tài).(因?yàn)樗窃诰幾g階段就進(jìn)行分配的機(jī)制).另外,C++中還有一種"假的"多態(tài)機(jī)制就是模板機(jī)制,同樣只是改變函數(shù)參數(shù)的類型,并不會改變函數(shù)具體的實(shí)現(xiàn)方式.
    發(fā)表于 10-01 17:18

    函數(shù)重載隱藏覆蓋的區(qū)別

    參數(shù)類型不同,或者參數(shù)個數(shù)和參數(shù)類型都不同),返回值類型可相同也可不同;這種情況叫做c++的重載!注意:c語言沒有函數(shù)
    發(fā)表于 05-11 09:33

    TWEN-ASR ONE 語音識別系列教程(4)---多線程與消息隊(duì)列使用

    TWEN-ASR ONE 語音識別系列教程(4)—多線程與消息隊(duì)列使用提示:作者使用 TWEN-ASR ONE V1.0開發(fā)板進(jìn)行開發(fā)學(xué)習(xí)。文章目錄前言一、多線程的使用與測試1.1線程
    發(fā)表于 07-02 16:27

    printf()h函數(shù)重載后還需要加入修改項(xiàng)函數(shù)重載

    printf()h函數(shù)重載后,還需要加入下圖的修改項(xiàng)函數(shù)重載時,在自定義頭文件中包含 stdio.h 文件,并加入以下重載
    發(fā)表于 08-23 09:33

    RT-Thread系統(tǒng)消息隊(duì)列常用的函數(shù)接口有哪些

    ,等待消息隊(duì)列線程按照優(yōu)先級的方式進(jìn)行排列。2. 發(fā)送消息RT-Thread 提供的發(fā)送消息接口函數(shù)有兩種:一種是無等待超時接口,一種是有等待超時。線程或者中斷服務(wù)程序都可以給消息
    發(fā)表于 03-31 14:14

    嵌入式-C++函數(shù)重載

    一、什么是函數(shù)重載 兩個以上的函數(shù),具有相同的函數(shù)名,通過參數(shù)的類型和參數(shù)的個數(shù)不同。編譯器自行匹配,自動確定調(diào)用哪一個
    發(fā)表于 06-28 13:54

    隊(duì)列FIFO——支持網(wǎng)絡(luò)QoS的重要芯片

    摘要:在IP網(wǎng)絡(luò)中支持QoS是近年來研究的熱點(diǎn),而IDT公司推出的新型存儲器件——隊(duì)列FIFO能夠支持QoS的應(yīng)用。因其具有單器件下支持
    發(fā)表于 03-11 13:22 ?1163次閱讀
    <b class='flag-5'>多</b><b class='flag-5'>隊(duì)列</b>FIFO——<b class='flag-5'>支持</b>網(wǎng)絡(luò)QoS的重要芯片

    線程安全隊(duì)列技術(shù)在交流電機(jī)調(diào)速系統(tǒng)中的應(yīng)用

    介紹了數(shù)據(jù)保護(hù)的概念和原理,根據(jù)交流電機(jī)調(diào)速系統(tǒng)的要求,使用線程安全隊(duì)列技術(shù)對電機(jī)的數(shù)據(jù)進(jìn)行保護(hù),以期能正確反映電機(jī)的運(yùn)行情況.通過實(shí)驗(yàn),對使用線程安全隊(duì)列技術(shù)的
    發(fā)表于 05-25 15:26 ?46次下載
    <b class='flag-5'>線程</b><b class='flag-5'>安全隊(duì)列</b>技術(shù)在交流電機(jī)調(diào)速系統(tǒng)中的應(yīng)用

    什么是線程安全

    線程安全的鏈表-隊(duì)列-棧,就是多線程同時操作(包括查找、添加、刪除等)鏈表、隊(duì)列或棧,無論如何操作,就是多
    發(fā)表于 11-17 11:16 ?1次下載

    C++之類中的函數(shù)重載學(xué)習(xí)的總結(jié)

    類的成員函數(shù)之間可以進(jìn)行重載;重載必須發(fā)生在同一個作用域中;全局函數(shù)和成員函數(shù)不能構(gòu)成重載關(guān)系;
    的頭像 發(fā)表于 12-24 17:02 ?510次閱讀

    uC/OS-II隊(duì)列原理 隊(duì)列相關(guān)函數(shù)介紹

    消息隊(duì)列線程間通訊的一種,主要用作數(shù)據(jù)緩沖,用途非常廣泛。一般情況下遵循先進(jìn)先出原則。
    的頭像 發(fā)表于 09-11 15:04 ?939次閱讀
    uC/OS-II<b class='flag-5'>隊(duì)列</b>原理 <b class='flag-5'>隊(duì)列</b>相關(guān)<b class='flag-5'>函數(shù)</b>介紹

    如何實(shí)現(xiàn)一個寫的線程安全的無鎖隊(duì)列

    加鎖。那么如何實(shí)現(xiàn)一個寫的線程安全的無鎖隊(duì)列呢? 互斥鎖:mutexqueue(太簡單不介紹了) 互斥鎖+條件變量:blockqueu
    的頭像 發(fā)表于 11-08 15:25 ?1315次閱讀
    如何實(shí)現(xiàn)一個<b class='flag-5'>多</b>讀<b class='flag-5'>多</b>寫的<b class='flag-5'>線程</b><b class='flag-5'>安全</b>的無鎖<b class='flag-5'>隊(duì)列</b>

    探索虛擬線程:原理與實(shí)現(xiàn)

    虛擬線程的引入與優(yōu)勢 在Loom項(xiàng)目之前,Java虛擬機(jī)(JVM)中的線程是通過java.lang.Thread類型來實(shí)現(xiàn)的,這些線程被稱為平臺線程
    的頭像 發(fā)表于 06-24 11:35 ?304次閱讀
    <b class='flag-5'>探索</b>虛擬<b class='flag-5'>線程</b>:原理與實(shí)現(xiàn)