You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:15:25