循環緩沖區

# 循環緩沖區

在這里插入圖片描述

說明

所謂消費,就是數據讀取并刪除。

循環緩沖區這個數據結構與生產者-消費者問題高度適配。

生產者-產生數據,消費者-處理數據,二者速度不一致,因此需要循環緩沖區。

顯然,產生的數據要追加到循環緩沖區末尾,然后再去消費

可以直接去最后拿代碼,看一般使用說明。

定義

// 定義緩沖區大小(可根據需要修改)
#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

123456
bufferreadP->writeP->end
------------------------------------------------------------------------------------------------

情況2

789123456
bufferwriteP->readP->end
------------------------------------------------------------------------------------------------

后面寫不下,回到開頭去寫。

注意消費指針、追加指針都只能“向后走”,走到最后就回到開頭。

追加、消費必須在對應位置去做。

注意

不允許將整個數組全部使用,否則無法區分當前是沒有數據,還是數據已滿。

當然也可以定義一個變量去記錄當前有多少數據,我這里不使用這種方法。

沒有數據

writeP->
readP->
------------------------------------------------------------------------------------------------

數據充滿整個數組

111212345678910
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個為例

原本

012
bufferreadPwritePend
------------------------------------------------------------------------------------------------

之后

0123456
bufferreadPwritePend
------------------------------------------------------------------------------------------------
需要返回開頭的情況

寫不下需要返回開頭!

以追加6個為例

原本

012
bufferreadPwritePend
------------------------------------------------------------------------------------------------

之后

567801234
bufferwritePreadPend
------------------------------------------------------------------------------------------------

實現代碼

/*** @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個數據為例

不需要返回開頭的情況

讀取前

1234567
bufferreadPwritePend
------------------------------------------------------------------------------------------------

讀取后

67
bufferreadPwritePend
------------------------------------------------------------------------------------------------
需要返回開頭的情況

讀取前

3456712
bufferwritePreadPend
------------------------------------------------------------------------------------------------

讀取后

67
bufferreadPwritePend
------------------------------------------------------------------------------------------------

實現代碼

/*** @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;
}

刪除一批數據

只給出迭代器,刪除它(不含)前面的所有數據的方法。

任意位置的刪除,會讓數據不再連續,要移動數據,這很復雜、而且沒什么用,不做討論。

說明

顯然

刪除一個迭代器前面的數據,只需要把頭指針移動到迭代器的位置。

刪除前

5678901234
bufferitwritePreadPend
------------------------------------------------------------------------------------------------

刪除后

789
bufferreadPwritePend
------------------------------------------------------------------------------------------------

實現代碼

/*** @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

在虛擬數組中

??????????
readPtailwriteP
1234
lastlastBehind
------------------------------------------------------------------------------------------------

在虛擬數組中最后一次查找的后面一個的位置是

lastBehindMap = tailMap - length + 1 + 1;
迭代器不需要返回開頭的情況

找到前

??1234?
bufferreadPwritePend
1234
it->
------------------------------------------------------------------------------------------------

找到后

??1234?
bufferreadPwritePend
1234
it
------------------------------------------------------------------------------------------------
迭代器需要返回開頭的情況

找到前

34????12
bufferwritePreadPend
1234
it->
------------------------------------------------------------------------------------------------

臨近尾部要把目標數據拆分成兩部分分別比較

34???12
bufferwritePreadPend
4123
it->
------------------------------------------------------------------------------------------------

找到后

34???12
bufferwritePreadPend
3412
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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/81538.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/81538.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/81538.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

嵌入式硬件篇---STM32 系列單片機型號命名規則

文章目錄 前言一、STM32 型號命名規則二、具體型號解析1. STM32F103C8T6F103:C:8:T6:典型應用2. STM32F103RCT6F103:R:C:T6:典型應用三、命名規則擴展1. 引腳數與封裝代碼2. Flash 容量代碼3. 溫度范圍代碼四、快速識別技巧性能定位:F1/F4后綴差異硬件設計參考:引腳數…

MySQL 中日期相減的完整指南

MySQL 中日期相減的完整指南 在 MySQL 中&#xff0c;日期相減有幾種不同的方法&#xff0c;具體取決于你想要得到的結果類型&#xff08;天數差、時間差等&#xff09;。 1. 使用 DATEDIFF() 函數&#xff08;返回天數差&#xff09; SELECT DATEDIFF(2023-05-15, 2023-05-…

傳奇各版本迭代時間及內容變化,屠龍/嗜魂法杖/逍遙扇第一次出現的時間和版本

?【早期經典版本】 1.10 三英雄傳說&#xff1a;2001 年 9 月 28 日熱血傳奇正式開啟公測&#xff0c;這是傳奇的第一個版本。游戲中白天與黑夜和現實同步&#xff0c;升級慢&#xff0c;怪物爆率低&#xff0c;玩家需要靠撿垃圾賣金幣維持游戲開銷&#xff0c;遇到高級別法師…

重塑數學邊界:人工智能如何引領數學研究的新紀元

目錄 一、人工智能如何重新定義數學研究的邊界 &#xff08;一&#xff09;數學與AI的關系&#xff1a;從基礎理論到創新思維的回饋 &#xff08;二&#xff09;AI的創造力&#xff1a;突破傳統推理的局限 &#xff08;三&#xff09;AI對數學研究的潛在貢獻&#xff1a;創…

IP偽裝、代理池與分布式爬蟲

一、動態代理IP應用&#xff1a;代理池的獲取、選擇與使用 代理池技術的核心是通過動態切換IP地址&#xff0c;讓爬蟲看起來像不同用戶在訪問網站&#xff0c;從而規避封禁。 &#xff08;一&#xff09;代理池的獲取途徑 1. 免費代理&#xff1a;低成本但高風險 免費代理可…

自然語言處理實戰:用CRF打造高精度命名實體識別系統

## 一、從標簽游戲到智能系統:命名實體識別的前世今生 在信息爆炸的互聯網時代,我們每天面對的海量文本中隱藏著無數有價值的信息。想象一下,當你在瀏覽新聞時,系統能自動標紅所有人名、地點和機構名稱——這就是命名實體識別(NER)技術的魔力。從早期的規則匹配到如今的…

Space Engineers 太空工程師 [DLC 解鎖] [Steam] [Windows]

Space Engineers 太空工程師 [DLC 解鎖] [Steam] [Windows] 需要有游戲正版基礎本體&#xff0c;安裝路徑不能帶有中文&#xff0c;或其它非常規拉丁字符&#xff1b; DLC 版本 至最新全部 DLC 后續可能無法及時更新文章&#xff0c;具體最新版本見下載文件說明 DLC 解鎖列表&…

JVM——JVM 是如何執行方法調用的?

JVM 是如何執行方法調用的&#xff1f; 在 Java 世界的底層運作中&#xff0c;方法調用機制是理解 Java 虛擬機&#xff08;JVM&#xff09;行為的關鍵之一。JVM 作為 Java 程序運行的核心&#xff0c;承擔著執行字節碼、管理內存、調度線程等多項職責。而方法調用作為程序邏輯…

MySQL 數據類型詳解:字符串、數字、日期

MySQL 數據類型詳解&#xff1a;字符串、數字、日期 在 MySQL 中&#xff0c;選擇合適的數據類型對于數據庫的存儲效率和查詢性能至關重要。MySQL 提供了**字符串&#xff08;String&#xff09;、數字&#xff08;Numeric&#xff09;和日期&#xff08;Date & Time&…

題解:P2485 [SDOI2011] 計算器

### 思路 本題是一個比較模板化的題目。 #### 一操作 考慮使用快速冪。 快速冪&#xff0c;只需要把 $k$ 變成二進制即可實現 $\Theta(\log k)$ 的時間復雜度。 實現方法&#xff1a; cpp long long qmi(long long a,long long k,long long p){ long long res 1; …

重新構想E-E-A-T:提升銷售與搜索可見性的SEO策略

在2025年的數字營銷環境中&#xff0c;谷歌的E-E-A-T&#xff08;經驗、專業性、權威性、可信度&#xff09;已成為SEO和內容營銷的核心支柱。傳統的E-E-A-T優化方法通常聚焦于展示作者資質或獲取反向鏈接&#xff0c;但這些策略可能不足以應對AI驅動的搜索和日益挑剔的用戶需求…

JVM 一文詳解

目錄 JVM 簡介 JVM 中的內存區域劃分 1. 堆&#xff08;一個進程只有一份 ------ 線程共享&#xff09; 2. 棧&#xff08;一個進程可以有 N 份 ------ 線程私有&#xff09; Java 虛擬機棧&#xff1a; 本機方法棧&#xff1a; 3. 程序計數器&#xff08;一個線程可以…

小程序與快應用:中國移動互聯網的漸進式革命——卓伊凡的技術演進觀

小程序與快應用&#xff1a;中國移動互聯網的漸進式革命——卓伊凡的技術演進觀 在知乎看到很多&#xff1a;“懂王”發布的要把內行笑瘋了的評論&#xff0c;卓伊凡必須懟一下&#xff0c;真印證那句話&#xff0c;無知者無畏 一、Web與小程序的技術本質差異 1.1 瀏覽器渲染…

[SC]SystemC在GPU/CPU SoC驗證中的應用案例

SystemC在GPU/CPU SoC驗證中的應用案例 摘要:SystemC 是一種基于 C++ 的系統級建模語言,廣泛用于 SoC (System on Chip) 設計的建模和驗證,尤其在 GPU SoC 驗證中,SystemC 可用于模擬硬件模塊、系統行為和性能評估。SystemC 的主要優勢在于支持系統級抽象建模、時序…

Java 網絡安全新技術:構建面向未來的防御體系

一、Java 安全架構的演進與挑戰 1.1 傳統安全模型的局限性 Java 平臺自 1995 年誕生以來&#xff0c;安全機制經歷了從安全管理器&#xff08;Security Manager&#xff09;到 Java 平臺模塊系統&#xff08;JPMS&#xff09;的演進。早期的安全管理器通過沙箱模型限制不可信…

sonar-scanner在掃描JAVA項目時為什么需要感知.class文件

1 概述 SonarQube是一個靜態代碼分析工具&#xff0c;主要用于檢查源代碼的質量&#xff0c;包括代碼重復、潛在漏洞、代碼風格問題等。而SonarScanner是SonarQube的客戶端工具&#xff0c;負責將代碼進行形態分析&#xff0c;并將結果發送到SonarQube服務器。所以&#xff0c…

媒資管理之視頻管理

一:業務概述: 媒資管理這個模塊是我負責開發的,主要的管理對象是視頻,圖片,文檔等 包括文件的上傳,視頻的處理,文件的刪除 (在媒資管理界面,有個上傳視頻的按鈕,視頻是在媒資這上傳的,課程圖片是在內容管理) 上傳的圖片和視頻,會單獨存儲到搭建的分布式文件系…

Maven 實現多模塊項目依賴管理

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家&#xff0c;歷代文學網&#xff08;PC端可以訪問&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移動端可微信小程序搜索“歷代文學”&#xff09;總架構師&#xff0c;15年工作經驗&#xff0c;精通Java編…

nuxt項目中引入并配置 iview

安裝iview npm install iview --save注&#xff1a;想要加入其它的配置&#xff0c;可以在 nuxt.config.js 的 plugins 配置項中加入&#xff0c;同時在 plugins 文件夾下加入引入邏輯。 在nuxt.config.js文件中寫&#xff1a; {src: ~plugins/iview, ssr: true}同時新建 plugi…

BG開發者日志505:項目總體情況

1、從2024年12月中旬啟動&#xff0c;到4月底gameplay部分開發完畢&#xff0c;已經四個半月過去了。 其中大部分內容是3、4兩個月中完成的&#xff0c;量產階段。 預計6月初參加新品節&#xff0c;6月中旬發售&#xff08;比原計劃7月中旬提前一個月&#xff09;。 --------…