【C】简易的环形缓冲区代码示例

一、简介

环形缓冲区是一种循环使用的线性数据结构,核心是用「数组 + 读写指针」模拟循环存储,解决普通线性缓冲区 "写满后无法复用已读空间" 的 "假满" 问题。

核心特点:

  1. 循环结构:数组末尾与开头逻辑相连,读写指针到达数组尾部后自动绕回头部;
  2. 读写指针read_idx(读指针,指向待读取数据)、write_idx(写指针,指向待写入位置);
  3. 判空 / 判满逻辑 (常用两种方式):
    • 预留 1 个空位置:(write_idx + 1) % capacity == read_idx 表示满,read_idx == write_idx 表示空;
    • 额外加计数变量:count == 0 空,count == capacity 满(本次代码用第一种,更简洁);
  4. 适用场景:生产者 - 消费者模型(如串口通信、日志缓存、多线程数据传输),需高效读写、固定容量的场景。

二、代码示例

ring_buffer.h

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 环形缓冲区结构体
typedef struct {
    void* buffer;     // 存储数据的缓冲区数组
    size_t      item_size;  // 单个元素的大小(字节)
    size_t      capacity;   // 缓冲区最大容量(元素个数)
    size_t      head;       // 头指针:下一个出队元素的索引
    size_t      tail;       // 尾指针:下一个入队元素的索引
    size_t      count;      // 当前已存储的元素个数(简化判空/判满逻辑)
} RingBuffer;


int RingBuffer_Init(RingBuffer* RB, size_t capacity, size_t item_size);
void RingBuffer_Destroy(RingBuffer* RB);
int RingBuffer_Is_Empty(const RingBuffer* RB);
int RingBuffer_Is_Full(const RingBuffer* RB);
size_t RingBuffer_Get_Count(const RingBuffer* RB);
int RingBuffer_Enqueue(RingBuffer* RB, const void* item);
int RingBuffer_Dequeue(RingBuffer* RB, void* item);
void RingBuffer_Print_Int(const RingBuffer* RB);

ring_buffer.c

cpp 复制代码
#include "ring_buffer.h"



int RingBuffer_Init(RingBuffer* RB, size_t capacity, size_t item_size) {
    if (RB == NULL || capacity == 0 || item_size == 0) {
        return -1; // 入参非法
    }

    // 分配缓冲区内存(连续空间)
    RB->buffer = malloc(capacity * item_size);
    if (RB->buffer == NULL) {
        return -1; // 内存分配失败
    }

    RB->item_size = item_size;
    RB->capacity = capacity;
    RB->head = 0;       // 初始头指针指向0
    RB->tail = 0;       // 初始尾指针指向0
    RB->count = 0;       // 初始无元素

    return 0;
}


void RingBuffer_Destroy(RingBuffer* RB) {
    if (RB != NULL) {
        free(RB->buffer); // 释放缓冲区数组
        RB->buffer = NULL; // 避免野指针
    }
}


int RingBuffer_Is_Empty(const RingBuffer* RB) {
    return (RB != NULL && RB->count == 0) ? 1 : 0;
}

int RingBuffer_Is_Full(const RingBuffer* RB) {
    return (RB != NULL && RB->count == RB->capacity) ? 1 : 0;
}


size_t RingBuffer_Get_Count(const RingBuffer* RB) {
    return (RB != NULL) ? RB->count : 0;
}


int RingBuffer_Enqueue(RingBuffer* RB, const void* item) {
    if (RB == NULL || item == NULL || RingBuffer_Is_Full(RB)) {
        return -1; // 入参非法或缓冲区满
    }

    // 拷贝元素到缓冲区尾部(避免直接指针操作,支持任意类型)
    memcpy(
        (char*)RB->buffer + RB->tail * RB->item_size, // 尾指针位置
        item,
        RB->item_size
    );

    // 尾指针循环移动(取模避免越界)
    RB->tail = (RB->tail + 1) % RB->capacity;
    RB->count++; // 元素个数+1

    return 0;
}

int RingBuffer_Dequeue(RingBuffer* RB, void* item) {
    if (RB == NULL || item == NULL || RingBuffer_Is_Empty(RB)) {
        return -1; // 入参非法或缓冲区空
    }

    // 从缓冲区头部拷贝元素到接收缓冲区
    memcpy(
        item,
        (char*)RB->buffer + RB->head * RB->item_size, // 头指针位置
        RB->item_size
    );

    // 头指针循环移动(取模避免越界)
    RB->head = (RB->head + 1) % RB->capacity;
    RB->count--; // 元素个数-1

    return 0;
}


void RingBuffer_Print_Int(const RingBuffer* RB) {
    if (RB == NULL || RB->item_size != sizeof(int) || RingBuffer_Is_Empty(RB)) {
        printf("Buffer is empty or not int-type\n");
        return;
    }

    printf("Buffer (count=%zu, capacity=%zu): ", RB->count, RB->capacity);
    size_t idx = RB->head;
    for (size_t i = 0; i < RB->count; i++) {
        // 按整型解析并打印
        int val = *(int*)((char*)RB->buffer + idx * RB->item_size);
        printf("%d ", val);
        idx = (idx + 1) % RB->capacity;
    }
    printf("\n");
}

main.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "ring_buffer.h"

// ------------------------------ 测试用例 ------------------------------
int main() {
    RingBuffer RB;
    const size_t CAPACITY = 5;  // 缓冲区容量5个元素
    const size_t ITEM_SIZE = sizeof(int); // 元素类型为int

    // 1. 初始化缓冲区
    if (RingBuffer_Init(&RB, CAPACITY, ITEM_SIZE) != 0) {
        printf("Buffer init failed\n");
        return -1;
    }

    // 2. 入队测试(正常入队)
    int items[] = { 10, 20, 30, 40, 50 };
    for (size_t i = 0; i < CAPACITY; i++) {
        if (RingBuffer_Enqueue(&RB, &items[i]) == 0) {
            printf("Enqueue: %d\n", items[i]);
        }
        else {
            printf("Enqueue failed: %d\n", items[i]);
        }
    }
    RingBuffer_Print_Int(&RB); // 输出:10 20 30 40 50

    // 3. 满队入队测试(预期失败)
    int full_item = 60;
    if (RingBuffer_Enqueue(&RB, &full_item) == -1) {
        printf("Enqueue failed (buffer full): %d\n", full_item);
    }

    // 4. 出队测试(正常出队)
    int deitem;
    if (RingBuffer_Dequeue(&RB, &deitem) == 0) {
        printf("Dequeue: %d\n", deitem); // 输出:10
    }
    RingBuffer_Print_Int(&RB); // 输出:20 30 40 50

    // 5. 入队+出队混合测试
    int new_item = 60;
    if (RingBuffer_Enqueue(&RB, &new_item) == 0) {
        printf("Enqueue: %d\n", new_item); // 入队60,缓冲区再次满
    }
    RingBuffer_Print_Int(&RB); // 输出:20 30 40 50 60

    // 6. 连续出队测试
    while (!RingBuffer_Is_Empty(&RB)) {
        RingBuffer_Dequeue(&RB, &deitem);
        printf("Dequeue: %d\n", deitem); // 依次输出20、30、40、50、60
    }

    // 7. 空队出队测试(预期失败)
    if (RingBuffer_Dequeue(&RB, &deitem) == -1) {
        printf("Dequeue failed (buffer empty)\n");
    }

    // 8. 销毁缓冲区
    RingBuffer_Destroy(&RB);

    return 0;
}

三、效果

相关推荐
Scott9999HH6 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社7 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海7 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖8 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大11 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai11 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio