# 循環緩沖區
說明
所謂消費,就是數據讀取并刪除。
循環緩沖區這個數據結構與生產者-消費者問題高度適配。
生產者-產生數據,消費者-處理數據,二者速度不一致,因此需要循環緩沖區。
顯然,產生的數據要追加到循環緩沖區末尾,然后再去消費。
可以直接去最后拿代碼,看一般使用說明。
定義
// 定義緩沖區大小(可根據需要修改)
#define CIRCULAR_BUFFER_SIZE 12// 結構體定義(對應C++類)
typedef struct {uint8_t buffer[CIRCULAR_BUFFER_SIZE]; // 內部存儲數組uint8_t* bufferEnd; // 指向 buffer[capacity - 1]uint8_t* writeP; // 寫指針(當前寫入位置)uint8_t* readP; // 讀指針(當前讀取位置)
} CircularBuffer;
其中
變量 | 說明 |
---|---|
readP | 將要消費內容的位置,應當指向數據區的開頭。 |
writeP | 將要追加內容的位置,應當指向數據區結尾的后一個。或者說“非數據區”的開頭 |
因為循環緩沖區與生產者-消費者問題高度適配,所以這樣設計是合理的。
情況1
1 | 2 | 3 | 4 | 5 | 6 | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | -> | writeP | -> | end | ||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
情況2
7 | 8 | 9 | 1 | 2 | 3 | 4 | 5 | 6 | |||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | -> | readP | -> | end | ||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
后面寫不下,回到開頭去寫。
注意消費指針、追加指針都只能“向后走”,走到最后就回到開頭。
追加、消費必須在對應位置去做。
注意
不允許將整個數組全部使用,否則無法區分當前是沒有數據,還是數據已滿。
當然也可以定義一個變量去記錄當前有多少數據,我這里不使用這種方法。
沒有數據
writeP | -> | ||||||||||
readP | -> | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
數據充滿整個數組
11 | 12 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
writeP | -> | ||||||||||
readP | |||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
初始化
/*** @brief 初始化* @param cb 循環緩沖區指針*/
void CircularBuffer_Init(CircularBuffer* cb);void CircularBuffer_Init(CircularBuffer* cb) {cb->bufferEnd = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;cb->writeP = cb->buffer;cb->readP = cb->buffer;
}
是否為空
/*** @brief 是否為空* @param cb 循環緩沖區指針* @return true 是* @return false 否*/
bool CircularBuffer_Empty(const CircularBuffer* cb);bool CircularBuffer_Empty(const CircularBuffer* cb) {return cb->writeP == cb->readP;
}
數據長度
當前存儲的數據長度,或者說允許消費的數據的長度
/*** @brief 數據長度* @param cb 循環緩沖區指針* @return 已存儲的數據長度*/
size_t CircularBuffer_Size(const CircularBuffer* cb);size_t CircularBuffer_Size(const CircularBuffer* cb) {return (cb->writeP - cb->readP + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)
}
總容量
/*** @brief 最大容量* @param cb 循環緩沖區指針* @return 最大容量*/
size_t CircularBuffer_Capacity(const CircularBuffer* cb);size_t CircularBuffer_Capacity(const CircularBuffer* cb) {return CIRCULAR_BUFFER_SIZE - 1;// 最大容量(總空間 - 1,因為不能完全填滿)
}
數據開頭位置
顯然,數據頭部head的位置就是readP。
/*** @brief 數據區開頭* @param cb 循環緩沖區指針* @return 數據區開頭指針*/
uint8_t* CircularBuffer_Begin(const CircularBuffer* cb);uint8_t* CircularBuffer_Begin(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;return cb->readP;
}
數據結尾位置
但要注意:數據最后一個tail并不是head + dataCount - 1!(見前面的例子)
應當這樣得到
數據結尾tail
tail = buffer + ((head + dataCount -1 - buffer + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)
其中
head + dataCount -1 - buffer 可能超過真實數組范圍,是一個“假想”的索引,還要進行取模運算!得到真實的索引。
/*** @brief 數據區結尾* @param cb 循環緩沖區指針* @return 數據區結尾指針*/
uint8_t* CircularBuffer_End(const CircularBuffer* cb);uint8_t* CircularBuffer_End(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;size_t tailIndex = (cb->readP + CircularBuffer_Size(cb) - 1 - cb->buffer + CIRCULAR_BUFFER_SIZE) % (CIRCULAR_BUFFER_SIZE);return (cb->buffer + tailIndex);
}
指針是否指向數據區
先排除指針根本不指向內部數組的情況。
然后注意,不要比較it和tail的真實位置,應當比較它們的虛擬位置
如果itMap <= tailMap 說明指針指向數據區
if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指針以數據頭為開頭的索引
size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//數據尾部,以數據頭為開頭的索引
if (itMapIndex > tailMapIndex) return false;//目標不在數據區內,錯誤
it - readP 可能為超出范圍,應當取模以修正
(it - readP) % (CIRCULAR_BUFFER_SIZE) 得到是,當循環緩沖區以head為開頭時,it的索引。
迭代器的“后”一個
迭代器(指針)it,它的后一個itBehind
注意itBehind并不一定是it++。
遍歷時應當
while(...)
{it++;if(it > bufferEnd) it=buffer;//超過范圍,回到開頭
}
當然,也可以這樣得到后一個的位置
itBehind = buffer + (it - buffer +1) % CIRCULAR_BUFFER_SIZE
同理,后n個
itNBehind = buffer + (it - buffer + n) % CIRCULAR_BUFFER_SIZE
追加一批數據
循環緩沖區使用時,來一批數據,就追加一批。
注意:為了提高拷貝效率,需要調用memcpy。memcpy有底層的優化。
并不是迭代器一直++,因此需要區分兩種情況。在真實的數組找到關鍵位置,然后調用memcpy
一般循環緩沖區用于生產者消費者模型,所以要使用這個和后面的消費一批數據。
說明
不需要返回開頭的情況
以追加4個為例
原本
0 | 1 | 2 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
之后
0 | 1 | 2 | 3 | 4 | 5 | 6 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
需要返回開頭的情況
寫不下需要返回開頭!
以追加6個為例
原本
0 | 1 | 2 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
之后
5 | 6 | 7 | 8 | 0 | 1 | 2 | 3 | 4 | |||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
實現代碼
/*** @brief 向緩沖區追加數據* @param cb 循環緩沖區指針* @param data 數據* @param length 數據長度* @return true 成功* @return false 失敗*/
bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length);bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length) {if (length == 0) return true;if (length > (CircularBuffer_Capacity(cb) - CircularBuffer_Size(cb))) return false;if (cb->writeP + length <= cb->bufferEnd) {memcpy(cb->writeP, data, length);cb->writeP += length;}else {size_t endLength = cb->bufferEnd - cb->writeP + 1;size_t beginLength = length - endLength;memcpy(cb->writeP, data, endLength);memcpy(cb->buffer, data + endLength, beginLength);cb->writeP = cb->buffer + beginLength;}return true;
}
消費一批數據
必須從頭消費。
說明
以消費5個數據為例
不需要返回開頭的情況
讀取前
1 | 2 | 3 | 4 | 5 | 6 | 7 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
讀取后
6 | 7 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
需要返回開頭的情況
讀取前
3 | 4 | 5 | 6 | 7 | 1 | 2 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
讀取后
6 | 7 | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
實現代碼
/*** @brief 從緩沖區消費數據* @param cb 循環緩沖區指針* @param data 數據* @param length 數據長度* @return true 成功* @return false 失敗 */
bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length);bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length) {if (length == 0) return true;if (length > CircularBuffer_Size(cb)) return false;if (cb->readP + length <= cb->bufferEnd) {memcpy(rBuffer, cb->readP, length);cb->readP += length;}else {size_t endLength = cb->bufferEnd - cb->readP + 1;size_t beginLength = length - endLength;memcpy(rBuffer, cb->readP, endLength);memcpy(rBuffer + endLength, cb->buffer, beginLength);cb->readP = cb->buffer + beginLength;}return true;
}
刪除一批數據
只給出迭代器,刪除它(不含)前面的所有數據的方法。
任意位置的刪除,會讓數據不再連續,要移動數據,這很復雜、而且沒什么用,不做討論。
說明
顯然
刪除一個迭代器前面的數據,只需要把頭指針移動到迭代器的位置。
例
刪除前
5 | 6 | 7 | 8 | 9 | 0 | 1 | 2 | 3 | 4 | ||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | it | writeP | readP | end | |||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
刪除后
7 | 8 | 9 | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
實現代碼
/*** @brief 刪除指針前的所有數據* @param cb 循環緩沖區指針* @param it 一個指向數據區結尾指針* @return true 成功* @return false 失敗*/
bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it);bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it) {if (CircularBuffer_Empty(cb)) return false;if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指針以數據頭為開頭的索引size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//數據尾部,以數據頭為開頭的索引if (itMapIndex > tailMapIndex) return false;//目標不在數據區內,錯誤cb->readP = it;return true;
}
查找數據
通常是要查找通信協議的數據頭
說明
以查找1 2 3 4為例
為了說明最后一次查找的位置last,我們弄個虛擬數組,他以數據區開頭位置為開頭。
為了之后比較方便,我們找last后面一個的位置lastBehind
在虛擬數組中
? | ? | ? | ? | ? | ? | ? | ? | ? | ? | ||
---|---|---|---|---|---|---|---|---|---|---|---|
readP | tail | writeP | |||||||||
1 | 2 | 3 | 4 | ||||||||
last | lastBehind | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
在虛擬數組中最后一次查找的后面一個的位置是
lastBehindMap = tailMap - length + 1 + 1;
迭代器不需要返回開頭的情況
找到前
? | ? | 1 | 2 | 3 | 4 | ? | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it | -> | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
找到后
? | ? | 1 | 2 | 3 | 4 | ? | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | readP | writeP | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it | |||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
迭代器需要返回開頭的情況
找到前
3 | 4 | ? | ? | ? | ? | 1 | 2 | ||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
1 | 2 | 3 | 4 | ||||||||
it | -> | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
臨近尾部要把目標數據拆分成兩部分分別比較
3 | 4 | ? | ? | ? | 1 | 2 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
4 | 1 | 2 | 3 | ||||||||
it | -> | ||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
找到后
3 | 4 | ? | ? | ? | 1 | 2 | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
buffer | writeP | readP | end | ||||||||
3 | 4 | 1 | 2 | ||||||||
it | |||||||||||
-------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
實現代碼
/*** @brief 查找特定數據* @param cb 循環緩沖區指針* @param target 目標* @param length 目標長度* @return NULL 沒有找到* @return 找到的目標指針
*/
uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length);uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length) {if (target == NULL) return NULL;if (length == 0) return CircularBuffer_Begin(cb);if (length > CircularBuffer_Size(cb)) return NULL;//虛擬數組中“最后一次比較的后一個”的索引size_t lastBehindMapIndex = CircularBuffer_Size(cb) - length + 1 + 1;//真實數組中“最后一次比較的后一個”的索引size_t lastBehindIndex = (cb->readP - cb->buffer + lastBehindMapIndex) % CIRCULAR_BUFFER_SIZE;//真實數組中“最后一次比較的后一個”的位置。uint8_t* lastBehind = cb->buffer + lastBehindIndex;uint8_t* it = cb->readP;while (it != lastBehind) {if (it + length <= cb->bufferEnd) {if (memcmp(it, target, length) == 0) {return it;}}else {size_t endLength = cb->bufferEnd - it + 1;size_t beginLength = length - endLength;if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0) {return it;}}it++;if (it > cb->bufferEnd) it = cb->buffer;//超過范圍,回到開頭}return NULL;
}
完整代碼
使用說明
生產者產生數據,追加進循環緩沖區->消費者查找協議頭->刪除前面的無效數據->消費數據包
append -> find -> eraseFront -> consume
C版本
Circularbuffer.h
#ifndef CIRCULAR_BUFFER_H
#define CIRCULAR_BUFFER_H#include <stdint.h>
#include <string.h>
#include <stdbool.h>
// 定義緩沖區大小(可根據需要修改)
#define CIRCULAR_BUFFER_SIZE 12// 結構體定義
typedef struct {uint8_t buffer[CIRCULAR_BUFFER_SIZE]; // 內部存儲數組uint8_t* bufferEnd; // 指向 buffer[capacity - 1]uint8_t* writeP; // 寫指針(當前寫入位置)uint8_t* readP; // 讀指針(當前讀取位置)
} CircularBuffer;// 函數聲明/*** @brief 初始化* @param cb 循環緩沖區指針*/
void CircularBuffer_Init(CircularBuffer* cb);
void CircularBuffer_Clear(CircularBuffer* cb);/*** @brief 數據長度* @param cb 循環緩沖區指針* @return 已存儲的數據長度*/
size_t CircularBuffer_Size(const CircularBuffer* cb);/*** @brief 最大容量* @param cb 循環緩沖區指針* @return 最大容量*/
size_t CircularBuffer_Capacity(const CircularBuffer* cb);/*** @brief 是否為空* @param cb 循環緩沖區指針* @return true 是* @return false 否*/
bool CircularBuffer_Empty(const CircularBuffer* cb);/*** @brief 數據區開頭* @param cb 循環緩沖區指針* @return 數據區開頭指針*/
uint8_t* CircularBuffer_Begin(const CircularBuffer* cb);/*** @brief 數據區結尾* @param cb 循環緩沖區指針* @return 數據區結尾指針*/
uint8_t* CircularBuffer_End(const CircularBuffer* cb);/*** @brief 向緩沖區追加數據* @param cb 循環緩沖區指針* @param data 數據* @param length 數據長度* @return true 成功* @return false 失敗*/
bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length);/*** @brief 從緩沖區消費數據* @param cb 循環緩沖區指針* @param data 數據* @param length 數據長度* @return true 成功* @return false 失敗 */
bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length);/*** @brief 刪除指針前的所有數據* @param cb 循環緩沖區指針* @param it 一個指向數據區結尾指針* @return true 成功* @return false 失敗*/
bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it);/*** @brief 查找特定數據* @param cb 循環緩沖區指針* @param target 目標* @param length 目標長度* @return NULL 沒有找到* @return 找到的目標指針
*/
uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length);#endif // CIRCULAR_BUFFER_H
Circularbuffe.c
#include "CircularBuffer.h"// 初始化緩沖區(對應C++構造函數)
void CircularBuffer_Init(CircularBuffer* cb) {cb->bufferEnd = cb->buffer + CIRCULAR_BUFFER_SIZE - 1;cb->writeP = cb->buffer;cb->readP = cb->buffer;
}// 清空緩沖區
void CircularBuffer_Clear(CircularBuffer* cb) {cb->writeP = cb->buffer;cb->readP = cb->buffer;
}size_t CircularBuffer_Size(const CircularBuffer* cb) {return (cb->writeP - cb->readP + CIRCULAR_BUFFER_SIZE) % CIRCULAR_BUFFER_SIZE)
}size_t CircularBuffer_Capacity(const CircularBuffer* cb) {return CIRCULAR_BUFFER_SIZE - 1;// 最大容量(總空間 - 1,因為不能完全填滿)
}bool CircularBuffer_Empty(const CircularBuffer* cb) {return cb->writeP == cb->readP;
}uint8_t* CircularBuffer_Begin(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;return cb->readP;
}uint8_t* CircularBuffer_End(const CircularBuffer* cb) {if (CircularBuffer_Empty(cb)) return NULL;size_t tailIndex = (cb->readP + CircularBuffer_Size(cb) - 1 - cb->buffer + CIRCULAR_BUFFER_SIZE) % (CIRCULAR_BUFFER_SIZE);return (cb->buffer + tailIndex);
}bool CircularBuffer_Append(CircularBuffer* cb, const uint8_t* data, size_t length) {if (length == 0) return true;if (length > (CircularBuffer_Capacity(cb) - CircularBuffer_Size(cb))) return false;if (cb->writeP + length <= cb->bufferEnd) {memcpy(cb->writeP, data, length);cb->writeP += length;}else {size_t endLength = cb->bufferEnd - cb->writeP + 1;size_t beginLength = length - endLength;memcpy(cb->writeP, data, endLength);memcpy(cb->buffer, data + endLength, beginLength);cb->writeP = cb->buffer + beginLength;}return true;
}bool CircularBuffer_Consume(CircularBuffer* cb, uint8_t* rBuffer, size_t length) {if (length == 0) return true;if (length > CircularBuffer_Size(cb)) return false;if (cb->readP + length <= cb->bufferEnd) {memcpy(rBuffer, cb->readP, length);cb->readP += length;}else {size_t endLength = cb->bufferEnd - cb->readP + 1;size_t beginLength = length - endLength;memcpy(rBuffer, cb->readP, endLength);memcpy(rBuffer + endLength, cb->buffer, beginLength);cb->readP = cb->buffer + beginLength;}return true;
}bool CircularBuffer_EraseFront(CircularBuffer* cb, uint8_t* it) {if (CircularBuffer_Empty(cb)) return false;if (it < cb->buffer || it > cb->bufferEnd) return false;size_t itMapIndex = (it - cb->readP) % (CIRCULAR_BUFFER_SIZE);//指針以數據頭為開頭的索引size_t tailMapIndex = CircularBuffer_Size(cb) - 1;//數據尾部,以數據頭為開頭的索引if (itMapIndex > tailMapIndex) return false;//目標不在數據區內,錯誤cb->readP = it;return true;
}uint8_t* CircularBuffer_Find(CircularBuffer* cb, const uint8_t* target, size_t length) {if (target == NULL) return NULL;if (length == 0) return CircularBuffer_Begin(cb);if (length > CircularBuffer_Size(cb)) return NULL;//虛擬數組中“最后一次比較的后一個”的索引size_t lastBehindMapIndex = CircularBuffer_Size(cb) - length + 1 + 1;//真實數組中“最后一次比較的后一個”的索引size_t lastBehindIndex = (cb->readP - cb->buffer + lastBehindMapIndex) % CIRCULAR_BUFFER_SIZE;//真實數組中“最后一次比較的后一個”的位置。uint8_t* lastBehind = cb->buffer + lastBehindIndex;uint8_t* it = cb->readP;while (it != lastBehind) {if (it + length <= cb->bufferEnd) {if (memcmp(it, target, length) == 0) {return it;}}else {size_t endLength = cb->bufferEnd - it + 1;size_t beginLength = length - endLength;if (memcmp(it, target, endLength) == 0 && memcmp(cb->buffer, target + endLength, beginLength) == 0) {return it;}}it++;if (it > cb->bufferEnd) it = cb->buffer;//超過范圍,回到開頭}return NULL;
}
C++版本
Circularbuffer.h
#pragma once#include <stddef.h>
#include <stdint.h>#define CIRCULAR_BUFFER_SIZE 12 //緩沖區大小,根據需要修改,必要的話動態吧,去構造函數中newclass CircularBuffer
{
private:uint8_t buffer[CIRCULAR_BUFFER_SIZE] = { 0x00 };//內部真正的數組uint8_t* const bufferEnd = buffer + CIRCULAR_BUFFER_SIZE - 1;//緩沖區真正的結尾uint8_t* writeP = this->buffer;//寫指針,追加的位置uint8_t* readP = this->buffer;//讀指針,消費數據的位置public:/*** @brief 清空此循環緩沖區* @note 可以使用此函數清空循環緩沖區*/void clear();public:/*** @brief 儲存的數據量* @return 儲存的數據量* @note 也就是未處理的數據長度*/size_t size();public:/*** @brief 最大容量* @return 最大容量*/size_t capacity();public:/*** @brief 是否為空* @return true 是* @return false 否*/bool empty();public:/*** @brief 數據區開頭* @return 有數據 數據區開頭的指針* @return 沒有數據 nullptr*/uint8_t* begin();public:/*** @brief 數據區結尾* @return 有數據 數據區最后一個的指針* @return 沒有數據 nullptr*/uint8_t* end();public:/*** @brief 向此緩沖區追加數據* @param data 要寫入的數據指針* @param length 要寫入的數據的長度* @return true 成功* @return false 失敗*/bool append(const uint8_t* data, size_t length);public:/*** @brief 從此循環緩沖區中讀出數據* @param rBuffer 讀取到的緩沖區* @param length 要求讀取的長度* @return true 成功* @return false 失敗*/bool consume(uint8_t* rBuffer, size_t length);public:/*** @brief 刪除迭代器前的所有數據* @param it 迭代器* @return true 成功* @return false 失敗*/bool eraseFront(uint8_t* it);public:/*** @brief 查找特定數據* @param target 目標數據指針* @param length 目標數據長度* @return 找到 目標位置* @return 沒有找到 nullptr*/uint8_t* find(const uint8_t* target, size_t length);public:/*** @brief 打印,測試用*/void printfCircularBuffer();
};
Circularbuffer.cpp
#include "CircularBuffer.h"
#include <string.h>void CircularBuffer::clear()
{//memset(cb, 0x00, CIRCULAR_BUFFER_SIZE);//其實不賦值也無所謂this->writeP = this->buffer;this->readP = this->buffer;
}size_t CircularBuffer::size()
{return ((this->writeP - this->readP + (this->capacity() + 1)) % (this->capacity() + 1));
}size_t CircularBuffer::capacity()
{return CIRCULAR_BUFFER_SIZE - 1;//內部數組不允許存滿
}bool CircularBuffer::append(const uint8_t* data, size_t length)
{if (length == 0)return true;if (length > (this->capacity() - this->size())) return false;//超過最大長度,錯誤if (this->writeP + length <= this->bufferEnd)//不需要返回開頭的情況{memcpy(this->writeP, data, length);this->writeP += length;}else//需要返回開頭的情況{size_t endLength = this->bufferEnd - this->writeP + 1;//結尾部分需要拷貝的數量size_t beginLength = length - endLength;//開頭部分需要拷貝的數量memcpy(this->writeP, data, endLength);//拷貝到后面 memcpy(this->buffer, data + endLength, beginLength);//拷貝到前面this->writeP = this->buffer + beginLength;}return true;
}bool CircularBuffer::empty()
{return this->writeP == this->readP;
}uint8_t* CircularBuffer::begin()
{if (this->empty()) return nullptr;//沒有數據return this->readP;
}uint8_t* CircularBuffer::end()
{if (this->empty()) return nullptr;//沒有數據size_t tailIndex = (this->readP + this->size() - 1 - this->buffer + this->capacity() + 1) % (this->capacity() + 1);return this->buffer + tailIndex;
}bool CircularBuffer::consume(uint8_t* rBuffer, size_t length)
{if (length == 0) return true;if (length > size()) return false;//超過當前存儲的長度,錯誤if (this->readP + length <= this->bufferEnd)//不需要返回開頭的情況{memcpy(rBuffer, this->readP, length);this->readP += length;}else//需要返回開頭的情況{size_t endLength = this->bufferEnd - this->readP + 1;//結尾部分需要拷貝的數量size_t beginLength = length - endLength;//開頭部分需要拷貝的數量memcpy(rBuffer, this->readP, endLength);//拷貝后面的memcpy(rBuffer + endLength, this->buffer, beginLength);//拷貝前面的this->readP = this->buffer + beginLength;}return true;
}bool CircularBuffer::eraseFront(uint8_t* it)
{if (this->empty()) return false;if (it<this->buffer || it>this->bufferEnd) return false;//不在真實數組范圍內,錯誤size_t itMapIndex = (it - this->begin()) % (this->capacity() + 1);//指針以數據頭為開頭的索引size_t tailMapIndex = this->size() - 1;//數據尾部,以數據頭為開頭的索引if (itMapIndex > tailMapIndex) return false;//目標不在數據區內,錯誤this->readP = it;return true;
}uint8_t* CircularBuffer::find(const uint8_t* target, size_t length)
{if (target == NULL) return nullptr;if (length == 0) return this->begin();if (length > this->size()) return nullptr;//目標長度超過當前數據長度,錯誤size_t lastBehindMapIndex = this->size() - length + 1 + 1;//虛擬數組中“最后一次比較的后一個”的索引size_t lastBehindIndex = (this->readP + lastBehindMapIndex - this->buffer) % (this->capacity() + 1);//真實數組中“最后一次比較的后一個”的索引uint8_t* lastBehind = this->buffer + lastBehindIndex;//真實數組中“最后一次比較的后一個”的位置。uint8_t* it = this->readP;//遍歷時的迭代器while (it != lastBehind){//目前不涉及迭代器返回開頭的情況if (it + length <= this->bufferEnd){if (memcmp(it, target, length) == 0){ return it;}}else{size_t endLength = this->bufferEnd - it + 1;//結尾部分需要比較的數量size_t beginLength = length - endLength;//開頭部分需要比較的數量if (memcmp(it, target, endLength) == 0 && memcmp(this->buffer, target + endLength, beginLength) == 0){return it;}}it++;if (it > this->bufferEnd) it = this->buffer;//超過結尾,回到開頭}return nullptr;
}#include <iostream>
#include <iomanip>
void CircularBuffer::printfCircularBuffer()
{std::cout << "Size: " << this->size() << ", Capacity: " << this->capacity()<< ", Empty: " << (this->empty() ? "true" : "false") << "\n";std::cout << "buffer" << std::endl;for (size_t i = 0; i < this->capacity(); ++i) {// 將 uint8_t 轉換為 int,并以兩位十六進制輸出std::cout << std::hex << std::uppercase<< std::setw(2) << std::setfill('0')<< static_cast<int>(this->buffer[i]) << " ";}std::cout << std::dec << std::endl; // 恢復十進制輸出格式std::cout << "data" << std::endl;uint8_t* it = this->begin();for (size_t i = 0; i < this->size(); i++){// 將 uint8_t 轉換為 int,并以兩位十六進制輸出std::cout << std::hex << std::uppercase<< std::setw(2) << std::setfill('0')<< static_cast<int>(*it) << " ";it = it + ((it - this->buffer + 1) % (this->capacity() + 1));//下一個位置}std::cout << std::dec << std::endl; // 恢復十進制輸出格式std::cout << "readP index " << (this->readP - this->buffer) << std::endl;std::cout << "writeP index " << (this->writeP - this->buffer) << std::endl;std::cout << "\n\n";
}
后記
循環緩沖區一般配合生產者、消費者問題使用。
生產者產生數據,追加進循環緩沖區->消費者查找協議頭->刪除前面的無效數據->消費數據包
append -> find -> eraseFront -> consume
嵌入式似乎很忌諱動態內存分配(本人目前不清楚為什么)。
如果嵌入式項目中涉及大量數據結構的問題,請使用ETL庫
ETL(Embedded Template Library) 是一個專為嵌入式系統和資源受限環境設計的輕量級 C++ 模板庫,提供了類似 STL(標準模板庫)的容器和算法,但更注重 確定性內存管理、零動態內存分配 和 低開銷。
etl::circular_buffer實現了循環緩沖區
但我沒看懂怎么用
參考
循環緩沖區其它講解:
https://docs.keysking.com/docs/stm32/example/UART_COMMAND
【【STM32】串口數據拆包?黏包?循環緩沖區幫你搞定!】 https://www.bilibili.com/video/BV1p75yzSEt9/?share_source=copy_web&vd_source=734d9fe130776e8ae82b2b5371a5f5b8
ETL說明
https://blog.csdn.net/gitblog_00682/article/details/142607246?fromshare=blogdetail&sharetype=blogdetail&sharerId=142607246&sharerefer=PC&sharesource=cctv1324&sharefrom=from_link