ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

102.环形缓冲区之读指针与写指针:原理、实现与完整代码

102.环形缓冲区之读指针与写指针:原理、实现与完整代码 一、环形缓冲区的核心概念环形缓冲区本质上是一个首尾相连的数组通过两个关键指针实现数据的读写管理写指针pw标记下一个数据写入的位置读指针pr标记下一个数据读取的位置这两个指针是环形缓冲区的灵魂所有的读写操作、状态判断都围绕它们展开。二、读写指针的工作原理1. 初始化状态缓冲区为空时写指针和读指针都指向数组的起始位置下标0。2. 写入数据流程第一次写入数据如01后写指针pw右移一格继续写入02、03……直到pw走到数组最后一格此时继续写入新数据如09pw会自动跳回数组下标0实现环形回绕不会发生数组越界再次写入10时pw继续向后移动3. 读取数据流程读指针pr逐格向右移动每读取一格对应位置的数据清空pr读完06后pw和pr同时移动模拟日常环形缓冲区运行时的一般情况当缓冲区内部所有数据全部读取完毕时pw和pr会指向同一个下标位置三、状态判断空与满的判定1. 空状态判断当pw等于pr时环形缓冲区为空。这是因为所有数据都已被读取完毕两个指针重新重合。2. 满状态判断环形缓冲区采用“牺牲一格”方案即数组中始终预留一个空白格子用于区分空和满状态。直观判断pw加一等于pr时缓冲区为满边界情况处理当pw在数组最后一位下标7pr在下标0时单纯数字8不等于0此时等式pw1pr不成立但缓冲区实际上已经存满通用公式(pw 1) % buffer_size pr这是全场景通用无bug的判满条件四、指针的实现代码1. 写指针实现// 方法一先自增再取模 pw; pw pw % buffer_size; // 方法二一行代码同时实现自增和取模 pw (pw 1) % buffer_size;2. 读指针实现// 方法一先自增再取模 pr; pr pr % buffer_size; // 方法二一行代码同时实现自增和取模 pr (pr 1) % buffer_size;这两种写法实现的功能完全一致都能保证指针在数组范围内循环移动。五、完整的环形缓冲区实现1. 头文件定义#ifndef RING_BUFFER_H #define RING_BUFFER_H #include stdint.h #include stdbool.h #define BUFFER_SIZE 8 // 缓冲区大小实际可用空间为BUFFER_SIZE - 1 typedef struct { uint8_t buffer[BUFFER_SIZE]; // 数据缓冲区 uint8_t pw; // 写指针 uint8_t pr; // 读指针 } RingBuffer_t; // 初始化环形缓冲区 void RingBuffer_Init(RingBuffer_t *rb); // 向环形缓冲区写入数据 bool RingBuffer_Write(RingBuffer_t *rb, uint8_t data); // 从环形缓冲区读取数据 bool RingBuffer_Read(RingBuffer_t *rb, uint8_t *data); // 判断环形缓冲区是否为空 bool RingBuffer_IsEmpty(RingBuffer_t *rb); // 判断环形缓冲区是否已满 bool RingBuffer_IsFull(RingBuffer_t *rb); #endif // RING_BUFFER_H2. 功能实现#include ring_buffer.h // 初始化环形缓冲区 void RingBuffer_Init(RingBuffer_t *rb) { rb-pw 0; rb-pr 0; } // 向环形缓冲区写入数据 bool RingBuffer_Write(RingBuffer_t *rb, uint8_t data) { // 检查缓冲区是否已满 if (RingBuffer_IsFull(rb)) { return false; } // 写入数据 rb-buffer[rb-pw] data; // 更新写指针 rb-pw (rb-pw 1) % BUFFER_SIZE; return true; } // 从环形缓冲区读取数据 bool RingBuffer_Read(RingBuffer_t *rb, uint8_t *data) { // 检查缓冲区是否为空 if (RingBuffer_IsEmpty(rb)) { return false; } // 读取数据 *data rb-buffer[rb-pr]; // 更新读指针 rb-pr (rb-pr 1) % BUFFER_SIZE; return true; } // 判断环形缓冲区是否为空 bool RingBuffer_IsEmpty(RingBuffer_t *rb) { return rb-pw rb-pr; } // 判断环形缓冲区是否已满 bool RingBuffer_IsFull(RingBuffer_t *rb) { return (rb-pw 1) % BUFFER_SIZE rb-pr; }3. 测试代码#include stdio.h #include ring_buffer.h int main() { RingBuffer_t rb; uint8_t data; // 初始化环形缓冲区 RingBuffer_Init(rb); // 写入数据 for (uint8_t i 0; i 7; i) { if (RingBuffer_Write(rb, i)) { printf(写入数据: %d\n, i); } else { printf(缓冲区已满无法写入数据: %d\n, i); } } // 尝试写入第8个数据应该失败 if (RingBuffer_Write(rb, 7)) { printf(写入数据: 7\n); } else { printf(缓冲区已满无法写入数据: 7\n); } // 读取数据 printf(\n读取数据:\n); while (!RingBuffer_IsEmpty(rb)) { if (RingBuffer_Read(rb, data)) { printf(读取数据: %d\n, data); } else { printf(缓冲区为空无法读取数据\n); } } // 尝试读取数据应该失败 if (RingBuffer_Read(rb, data)) { printf(读取数据: %d\n, data); } else { printf(缓冲区为空无法读取数据\n); } return 0; }六、总结环形缓冲区的核心优势在于读指针和写指针不会卡死在数组末尾而是会自动循环回到头部实现数据的连续读写。通过“牺牲一格”的方案我们可以准确判断缓冲区的空和满状态避免数据覆盖和丢失。
返回列表