我正在使用环形缓冲区来跟踪嵌入式c应用程序的下载字节数。在这个小例子中,当环形缓冲区中有5个字节时,我想要“清空”缓冲区。在实际应用程序中,我将处理要写入闪存的字节。我的问题是,我一直在跳过第5个字节。这是我的代码:
#include <stdio.h>
#include <stdint.h>
uint8_t outData;
uint8_t myDatBuf[32];
typedef struct {
uint8_t * buffer;
int head;
int tail;
int maxLen;
} ring_buffer_t;
ring_buffer_t rb;
int ring_buffer_get(ring_buffer_t *c, uint8_t *data);
int ring_buffer_put(ring_buffer_t *c, uint8_t data);
int ring_buffer_full(ring_buffer_t *c);
int ring_buffer_has_data(ring_buffer_t *c);
int main()
{
uint8_t a = 0;
rb.buffer = myDatBuf;
rb.head = 0;
rb.tail = 0;
rb.maxLen = 5;
for (uint8_t i = 0; i < 12; i++) {
if (ring_buffer_put(&rb, i) == -1) { // rb is full
printf("Ring Buffer is full! Lets empty it\n\r");
for (int x = 0; x < 5; x++) {
if (ring_buffer_get(&rb, &outData) == - 1) {
printf("Buffer is Empty\n\r");
} else {
printf("val: %d\n\r", outData);
}
}
}
}
printf("Empty the remaining bytes\n\r");
while (ring_buffer_has_data(&rb)) {
if (ring_buffer_get(&rb, &outData) == -1) {
printf("Buffer is Empty\n\r");
}
else {
printf("Rest: %d\n\r", outData);
}
}
return 0;
}
int ring_buffer_put(ring_buffer_t *c, uint8_t data)
{
// next is where head will point to after this write.
int next = c->head + 1;
if (next >= c->maxLen) {
next = 0;
}
if (next == c->tail) { // check if circular buffer is full
return -1;
} // and return with an error.
c->buffer[c->head] = data; // Load data and then move
c->head = next; // head to next data offset.
return 0; // return success to indicate successful push.
}
int ring_buffer_get(ring_buffer_t *c, uint8_t *data)
{
// if the head isn't ahead of the tail, we don't have any characters
if (c->head == c->tail) // check if circular buffer is empty
return -1; // and return with an error
// next is where tail will point to after this read.
int next = c->tail + 1;
if (next >= c->maxLen)
next = 0;
uint8_t t = c->tail;
*data = c->buffer[t]; // Read data and then move
c->tail = next; // tail to next data offset.
return 0; // return success to indicate successful pop.
}
int ring_buffer_full(ring_buffer_t *c) {
return c->head == c->maxLen ? 1 : 0;
}
int ring_buffer_has_data(ring_buffer_t *c) {
return (c->head != c->tail) ? 1 : 0;
}
这是输出 - 如您所见,跳过了数组中的第5个元素。任何帮助表示赞赏。
Ring Buffer is full! Lets empty it
val: 0
val: 1
val: 2
val: 3
Buffer is Empty
Ring Buffer is full! Lets empty it
val: 5
val: 6
val: 7
val: 8
Buffer is Empty
Empty the remaining bytes
Rest: 10
Rest: 11
答案 0 :(得分:3)
问题是head == tail
是一个模棱两可的情况。这是否意味着缓冲区是空的,还是意味着它已满?你不可能两种方式。您似乎意识到了这一点,因为ring_buffer_put
在head
等于tail
之前返回错误,从而避免了歧义。 ring_buffer_get
还假设head == tail
表示空缓冲区。事实上,这样一个maxLen == 5
的环形缓冲区只能包含4个元素!
那么为什么价值4
似乎丢失了?只是因为如果ring_buffer_put
返回-1
,则刷新缓冲区,但不会再尝试将值放入缓冲区。所以问题不在于环形缓冲区实现本身(对我来说看起来很好),但main
中的代码驱动它。
您可以通过让ring_buffer_put
执行put操作来解决这个问题,然后然后检查缓冲区是否已满。或者,您可以在尝试写入缓冲区之前添加一个单独的ring_buffer_is_full
函数。
答案 1 :(得分:2)
现在是学习如何使用调试器的时候了。您的循环缓冲区当前具有以下约定:
这意味着您只能存储maxlen - 1个字符!
无论如何,如果你想区分缓冲区满和缓冲区为空,还有更多的事情要做。
(*)ring_buffer_full
的当前实现是错误的......
答案 2 :(得分:1)
正如其他答案所说,你必须区分缓冲区满和缓冲区空,并且一种方法是保持一个小于缓冲区大小。但这会使缓冲区维护更加困难,因为它是一个环形缓冲区,如果没有摆弄,你就无法进行直接比较。
所以我通常的方法是为缓冲区中的项目数量设置另一个变量,这样可以让作者轻松判断它是否已满,并且读者可以判断它是否为空。该变量需要定义为volatile
,例如volatile int bufcount
,因此它不会受编译器优化的影响。
它也应该是atomic
,以保证在单个操作中使用递增或递减指令而不是读取 - 修改 - 写入来改变其值不会被中断。