C语言通用队列模块处理多种结构体类型的实现问询
这是个非常实用的通用队列需求,用字节数组做底层存储来适配不同结构体的思路完全可行——本质就是把队列当成一个通用字节缓冲区,通过字节拷贝实现类型无关的存储/读取。我给你一套完整的实现方案,同时帮你避开几个容易踩的坑:
1. 通用队列模块设计
首先我们需要把队列的核心接口抽象出来,只关心字节流,不关心具体存储的结构体类型。
queue.h(头文件)
#ifndef QUEUE_H #define QUEUE_H #include <stdint.h> #include <stdbool.h> // 通用队列结构体:存储缓冲区信息和队列状态 typedef struct { uint8_t* buffer; // 底层字节缓冲区 size_t buffer_size; // 缓冲区总字节数 size_t head; // 队头字节索引 size_t tail; // 队尾字节索引 size_t element_cnt; // 当前队列中的元素个数 } queue_t; // 初始化队列:传入预先分配的缓冲区和大小 bool queue_init(queue_t* queue, uint8_t* buffer, size_t buffer_size); // 入队:传入数据指针和数据的字节大小 bool queue_push(queue_t* queue, const void* data, size_t data_size); // 出队:传入存储结果的指针和期望的数据字节大小(必须和入队时一致) bool queue_pop(queue_t* queue, void* data, size_t data_size); // 判断队列是否为空 bool queue_is_empty(const queue_t* queue); // 判断队列是否能容纳下指定大小的数据 bool queue_is_full(const queue_t* queue, size_t data_size); #endif // QUEUE_H
queue.c(实现文件)
#include "queue.h" #include <string.h> bool queue_init(queue_t* queue, uint8_t* buffer, size_t buffer_size) { if (queue == NULL || buffer == NULL || buffer_size == 0) { return false; } queue->buffer = buffer; queue->buffer_size = buffer_size; queue->head = queue->tail = queue->element_cnt = 0; return true; } bool queue_push(queue_t* queue, const void* data, size_t data_size) { if (queue == NULL || data == NULL || data_size == 0) { return false; } // 检查剩余空间是否足够 if (queue_is_full(queue, data_size)) { return false; } // 字节拷贝入队 memcpy(&queue->buffer[queue->tail], data, data_size); // 更新队尾索引(循环队列逻辑) queue->tail = (queue->tail + data_size) % queue->buffer_size; queue->element_cnt++; return true; } bool queue_pop(queue_t* queue, void* data, size_t data_size) { if (queue == NULL || data == NULL || data_size == 0 || queue_is_empty(queue)) { return false; } // 字节拷贝出队 memcpy(data, &queue->buffer[queue->head], data_size); // 更新队头索引 queue->head = (queue->head + data_size) % queue->buffer_size; queue->element_cnt--; return true; } bool queue_is_empty(const queue_t* queue) { return (queue != NULL) && (queue->element_cnt == 0); } bool queue_is_full(const queue_t* queue, size_t data_size) { if (queue == NULL) { return true; } // 计算剩余可用空间(处理循环队列的头尾环绕情况) size_t free_space = (queue->tail >= queue->head) ? (queue->buffer_size - queue->tail + queue->head) : (queue->head - queue->tail); return free_space < data_size; }
2. 任务侧的使用示例
现在task1和task2可以直接用这个队列模块存储各自的结构体,不需要修改队列代码。
task1.c(Task1的业务代码)
#include "queue.h" #include <stdio.h> // Task1专属结构体 typedef struct { uint32_t task_id; float temperature; uint8_t status; } task_1_t; // Task1入队示例 void task1_push_sample(queue_t* queue) { task_1_t sample = {.task_id = 1, .temperature = 25.7f, .status = 0x01}; if (queue_push(queue, &sample, sizeof(task_1_t))) { printf("[Task1] 数据入队成功\n"); } else { printf("[Task1] 入队失败:队列已满\n"); } } // Task1出队示例 void task1_pop_sample(queue_t* queue) { task_1_t sample; if (queue_pop(queue, &sample, sizeof(task_1_t))) { printf("[Task1] 出队数据:ID=%d, 温度=%.1f℃, 状态=0x%02X\n", sample.task_id, sample.temperature, sample.status); } else { printf("[Task1] 出队失败:队列为空\n"); } }
task2.c(Task2的业务代码)
#include "queue.h" #include <stdio.h> // Task2专属结构体 typedef struct { uint16_t sensor_id; uint32_t timestamp; int16_t humidity; } task_2_t; // Task2入队示例 void task2_push_sample(queue_t* queue) { task_2_t sample = {.sensor_id = 0x1002, .timestamp = 1700000000, .humidity = 62}; if (queue_push(queue, &sample, sizeof(task_2_t))) { printf("[Task2] 数据入队成功\n"); } else { printf("[Task2] 入队失败:队列已满\n"); } } // Task2出队示例 void task2_pop_sample(queue_t* queue) { task_2_t sample; if (queue_pop(queue, &sample, sizeof(task_2_t))) { printf("[Task2] 出队数据:传感器ID=0x%04X, 时间戳=%u, 湿度=%d%%\n", sample.sensor_id, sample.timestamp, sample.humidity); } else { printf("[Task2] 出队失败:队列为空\n"); } }
main.c(主程序)
#include "queue.h" #include "task1.h" #include "task2.h" // 定义队列缓冲区大小:按实际需求分配,这里能存2个task1+2个task2的结构体 #define QUEUE_BUF_SIZE (2*sizeof(task_1_t) + 2*sizeof(task_2_t)) static uint8_t queue_buffer[QUEUE_BUF_SIZE]; static queue_t my_queue; int main() { // 初始化队列 if (!queue_init(&my_queue, queue_buffer, QUEUE_BUF_SIZE)) { printf("队列初始化失败\n"); return -1; } // 模拟任务交替入队 task1_push_sample(&my_queue); task2_push_sample(&my_queue); task1_push_sample(&my_queue); task2_push_sample(&my_queue); // 再入队一个,应该失败(队列已满) task1_push_sample(&my_queue); // 全部出队 task1_pop_sample(&my_queue); task2_pop_sample(&my_queue); task1_pop_sample(&my_queue); task2_pop_sample(&my_queue); // 再出队,应该失败(队列为空) task1_pop_sample(&my_queue); return 0; }
3. 避坑指南
这个方案好用,但有几个细节必须注意:
- 内存对齐问题:结构体在编译时会自动对齐(比如32位系统下float会对齐到4字节),静态分配的uint8数组一般没问题,但如果是动态分配缓冲区,要确保起始地址满足最大对齐要求(可以用
alignas关键字,或者手动调整malloc的返回地址)。 - 类型安全风险:出队时必须传入和入队时完全相同的结构体大小,否则会出现内存越界或数据解析错误。建议用宏封装专属的入队出队操作,比如:
#define TASK1_PUSH(q, d) queue_push(q, d, sizeof(task_1_t)) #define TASK1_POP(q, d) queue_pop(q, d, sizeof(task_1_t)) - 多任务安全:如果task1和task2在不同线程/RTOS任务中操作队列,必须加互斥锁(比如RTOS的mutex),否则会出现队列状态混乱。
- 缓冲区大小:提前估算最大存储需求,避免缓冲区不足导致入队失败。嵌入式场景尽量用静态分配,避免动态内存的不确定性。
内容的提问来源于stack exchange,提问作者Otus
相关产品推荐
相关产品推荐

