如何在C语言中对void*内存块进行结构化数据读写?
在预分配void*缓冲区读写不同类型数据的实用方案
核心逻辑
为了避开频繁malloc/free的性能坑,我们预分配一块连续内存作为命令缓冲区,靠偏移计算和类型强转直接在缓冲区对应位置读写结构体即可——重点是要保证内存对齐和偏移计算的准确性。
关键函数实现
先补全你代码里的buffer_alloc和buffer_get_ptr,这两个是操作缓冲区的核心:
buffer_alloc 实现
这个函数负责从指定偏移位置返回对应类型的指针,同时加入边界检查防止缓冲区溢出:
#include <stddef.h> #include <stdlib.h> #include <stdio.h> // 给缓冲区加个头部,记录总大小和已用空间,方便管理 typedef struct { size_t total_size; size_t used_size; char data[]; // 柔性数组存储实际命令数据 } CmdBufferHeader; void* buffer_alloc(void* buffer, size_t offset, size_t size) { CmdBufferHeader* header = (CmdBufferHeader*)buffer; // 检查偏移+数据大小是否超出缓冲区总容量 if (offset + size > header->total_size - sizeof(CmdBufferHeader)) { return NULL; // 内存不足 } // 更新已用空间(循环复用缓冲区时有用) if (offset + size > header->used_size) { header->used_size = offset + size; } // 转成char*计算偏移,因为void*不能直接进行算术运算 return (void*)(header->data + offset); }
buffer_get_ptr 实现
这个函数更简单,就是根据偏移直接返回对应位置的指针:
void* buffer_get_ptr(void* buffer, size_t offset) { CmdBufferHeader* header = (CmdBufferHeader*)buffer; // 防越界检查 if (offset >= header->total_size - sizeof(CmdBufferHeader)) { return NULL; } return (void*)(header->data + offset); }
修正后的主函数示例
注意原malloc需要调整,因为我们给缓冲区加了头部,要分配头部+数据区的总大小:
// 命令结构体定义 struct cmd_viewport { int x, y, w, h; }; struct cmd_line { int x0, y0, x1, y1; unsigned int width; }; int main(int argc, char* argv[]) { // 预分配1024字节的缓冲区(头部+数据) size_t buffer_total_size = 1024; CmdBufferHeader* cmd_buffer = (CmdBufferHeader*)malloc(buffer_total_size); if (!cmd_buffer) { return EXIT_FAILURE; } // 初始化缓冲区头部 cmd_buffer->total_size = buffer_total_size; cmd_buffer->used_size = 0; // 分配viewport命令,偏移从0开始 struct cmd_viewport* viewport = (struct cmd_viewport*)buffer_alloc(cmd_buffer, 0, sizeof(struct cmd_viewport)); // 分配line命令,偏移为viewport结构体的大小 struct cmd_line* line = (struct cmd_line*)buffer_alloc(cmd_buffer, sizeof(struct cmd_viewport), sizeof(struct cmd_line)); if (!viewport || !line) { free(cmd_buffer); return EXIT_FAILURE; } // 直接往缓冲区对应位置写入数据 viewport->x = 0; viewport->y = 0; viewport->w = 800; viewport->h = 600; line->x0 = 100; line->y0 = 100; line->x1 = 300; line->y1 = 400; line->width = 2; // 从指定偏移读取数据 struct cmd_line* line2 = (struct cmd_line*)buffer_get_ptr(cmd_buffer, sizeof(struct cmd_viewport)); if (line2) { printf("line->width = %u\n", line2->width); } // 循环复用缓冲区时,只需重置used_size,无需重复malloc/free cmd_buffer->used_size = 0; free(cmd_buffer); return EXIT_SUCCESS; }
必注意的细节
- 内存对齐:C语言中结构体成员会自动对齐,
malloc分配的内存本身就是对齐的,因此直接转换指针是安全的,无需额外处理对齐问题。 - 偏移计算:必须用
char*来计算偏移,因为char占1字节,偏移值就是准确的字节数,而void*不能直接进行加减运算。 - 边界检查:一定要添加边界检查,避免缓冲区溢出,尤其是循环复用缓冲区时,要确保写入的数据不超过预分配的大小。
- 循环复用技巧:每秒60次的循环场景中,每次循环开始时将
used_size设为0,直接重新写入命令即可,完全不需要重复执行malloc/free,能大幅提升性能。
类型转换的本质
从void*缓冲区的指定偏移转换为任意数据类型,本质就是三步:
- 将
void*转换为char*(char是最小内存单位,偏移计算精准) - 加上指定的偏移量,得到目标位置的
char*指针 - 将该
char*指针转换为目标数据类型的指针
比如你代码里的buffer_get_ptr,核心逻辑就是(void*)((char*)buffer_data + offset),添加头部只是为了更方便地管理缓冲区。
内容的提问来源于stack exchange,提问作者flcl8193
相关产品推荐
相关产品推荐

