C语言实现环形缓冲区(Ring Buffer)详解

📅 发布时间:2026/8/27 9:09:33
C语言实现环形缓冲区(Ring Buffer)详解 1. 什么是环形缓冲区环形缓冲区Ring Buffer / Circular Buffer是一种固定大小的先进先出FIFO数据结构。它在内存中表现为一个首尾相连的环形数组当写入数据到达缓冲区末尾时会自动从缓冲区开头继续写入前提是还有空间。这种结构非常适合在生产者-消费者模型中使用例如串口接收、音频处理、网络数据缓存等场景。与普通线性缓冲区相比环形缓冲区可以避免频繁的内存搬移只需移动读写指针即可效率很高。2. 设计思路我们采用计数器法来判断缓冲区满/空这是一种直观且不容易出错的方法使用head指向下一个要读取的位置读指针。使用tail指向下一个要写入的位置写指针。使用size记录缓冲区中当前有效数据的字节数。缓冲区总容量为capacity。判断条件缓冲区为空size 0缓冲区已满size capacity可读数据量size可写空间量capacity - size当写入数据时如果剩余空间不足可以选择部分写入或返回错误。本实现采用最多写入不超过剩余空间的策略并返回实际写入的字节数。3. 完整代码实现以下代码使用标准C库C99及以上包含初始化、释放、写入、读取、清空以及辅助查询函数。#includestdio.h#includestdlib.h#includestring.h#includestdint.h#includestdbool.htypedefstruct{uint8_t*buffer;// 数据存储区size_tcapacity;// 缓冲区总容量字节size_thead;// 读指针下一个要读取的位置size_ttail;// 写指针下一个要写入的位置size_tsize;// 当前有效数据字节数}ring_buffer_t;/** * 初始化环形缓冲区 * param rb 缓冲区结构体指针 * param capacity 期望的容量字节 * return true:成功, false:内存分配失败 */boolring_buffer_init(ring_buffer_t*rb,size_tcapacity){if(rbNULL||capacity0)returnfalse;rb-buffer(uint8_t*)malloc(capacity);if(rb-bufferNULL)returnfalse;rb-capacitycapacity;rb-head0;rb-tail0;rb-size0;returntrue;}/** * 释放环形缓冲区占用的内存 */voidring_buffer_free(ring_buffer_t*rb){if(rbrb-buffer){free(rb-buffer);rb-bufferNULL;rb-capacity0;rb-headrb-tailrb-size0;}}/** * 清空缓冲区不释放内存 */voidring_buffer_reset(ring_buffer_t*rb){if(rb){rb-head0;rb-tail0;rb-size0;}}/** * 获取缓冲区中可读的字节数 */size_tring_buffer_readable(constring_buffer_t*rb){return(rb)?rb-size:0;}/** * 获取缓冲区中可写的空闲字节数 */size_tring_buffer_writable(constring_buffer_t*rb){return(rb)?(rb-capacity-rb-size):0;}/** * 判断缓冲区是否为空 */boolring_buffer_is_empty(constring_buffer_t*rb){return(rbNULL||rb-size0);}/** * 判断缓冲区是否已满 */boolring_buffer_is_full(constring_buffer_t*rb){return(rbrb-sizerb-capacity);}/** * 向缓冲区写入数据最多写入可写空间的字节数 * param rb 缓冲区结构体指针 * param data 源数据指针 * param len 期望写入的字节数 * return 实际写入的字节数 */size_tring_buffer_write(ring_buffer_t*rb,constuint8_t*data,size_tlen){if(rbNULL||dataNULL||len0)return0;size_twritablering_buffer_writable(rb);if(writable0)return0;// 缓冲区已满无法写入if(lenwritable)lenwritable;// 限制写入长度// 计算从tail到缓冲区末尾的连续空间size_tto_endrb-capacity-rb-tail;if(lento_end){// 一次拷贝即可memcpy(rb-bufferrb-tail,data,len);}else{// 需要分两段拷贝先末尾再开头memcpy(rb-bufferrb-tail,data,to_end);memcpy(rb-buffer,datato_end,len-to_end);}rb-tail(rb-taillen)%rb-capacity;rb-sizelen;returnlen;}/** * 从缓冲区读取数据最多读取可读数据的字节数 * param rb 缓冲区结构体指针 * param out 目标数据指针 * param len 期望读取的字节数 * return 实际读取的字节数 */size_tring_buffer_read(ring_buffer_t*rb,uint8_t*out,size_tlen){if(rbNULL||outNULL||len0)return0;size_treadablering_buffer_readable(rb);if(readable0)return0;// 缓冲区为空无法读取if(lenreadable)lenreadable;// 限制读取长度// 计算从head到缓冲区末尾的连续空间size_tto_endrb-capacity-rb-head;if(lento_end){memcpy(out,rb-bufferrb-head,len);}else{memcpy(out,rb-bufferrb-head,to_end);memcpy(outto_end,rb-buffer,len-to_end);}rb-head(rb-headlen)%rb-capacity;rb-size-len;returnlen;}/** * 查看缓冲区中第一个字节不移动读指针 * param rb 缓冲区结构体指针 * param byte 输出参数存放读取到的字节 * return true:成功读取, false:缓冲区为空 */boolring_buffer_peek(constring_buffer_t*rb,uint8_t*byte){if(rbNULL||byteNULL||rb-size0)returnfalse;*byterb-buffer[rb-head];returntrue;}4. 使用示例下面演示如何创建一个容量为10字节的环形缓冲区进行写入、读取操作并处理缓冲区满和空的情况。intmain(void){ring_buffer_trb;uint8_ttest_data[20];uint8_tout_buf[20];size_twritten,read;// 初始化缓冲区容量10字节if(!ring_buffer_init(rb,10)){printf(初始化失败\n);return-1;}// 准备测试数据for(inti0;i20;i){test_data[i]i;}// 写入15字节数据实际只写入10字节缓冲区满writtenring_buffer_write(rb,test_data,15);printf(尝试写入15字节实际写入: %zu\n,written);printf(可读: %zu, 可写: %zu\n,ring_buffer_readable(rb),ring_buffer_writable(rb));// 读取5字节readring_buffer_read(rb,out_buf,5);printf(读取 %zu 字节: ,read);for(size_ti0;iread;i)printf(%d ,out_buf[i]);printf(\n);printf(可读: %zu, 可写: %zu\n,ring_buffer_readable(rb),ring_buffer_writable(rb));// 继续写入5字节writtenring_buffer_write(rb,test_data,5);printf(写入 %zu 字节\n,written);printf(可读: %zu, 可写: %zu\n,ring_buffer_readable(rb),ring_buffer_writable(rb));// 读取全部数据readring_buffer_read(rb,out_buf,sizeof(out_buf));printf(读取 %zu 字节: ,read);for(size_ti0;iread;i)printf(%d ,out_buf[i]);printf(\n);printf(缓冲区是否为空: %s\n,ring_buffer_is_empty(rb)?是:否);// 释放内存ring_buffer_free(rb);return0;}5. 运行结果尝试写入15字节实际写入: 10 可读: 10, 可写: 0 读取 5 字节: 0 1 2 3 4 可读: 5, 可写: 5 写入 5 字节 可读: 10, 可写: 0 读取 10 字节: 5 6 7 8 9 0 1 2 3 4 缓冲区是否为空: 是6. 总结本文实现了一个基于计数器法的环形缓冲区具有以下特点使用标准C库无平台依赖。支持任意字节流操作适合嵌入式或高性能场景。提供完整的读写接口和状态查询。通过示例验证了缓冲区的环形特性和边界处理。你可以将此代码直接用于自己的项目中也可以根据需要扩展为线程安全版本例如添加互斥锁。希望这篇博客能帮助你理解和应用环形缓冲区。