需求:支持将非相邻内存缓冲区转为连续视图的数据结构
实现虚拟连续的分散-聚集缓冲区结构
这个需求我之前做流媒体数据处理时碰到过,本质上就是要实现一个虚拟连续的分散-聚集(scatter-gather)缓冲区——把物理上不相邻的内存块,对外暴露成一块逻辑上完全连续的大缓冲区。下面给你拆解具体的实现思路、API逻辑和注意事项:
核心内部数据结构
首先得在类里维护好所有添加进来的缓冲区元数据,方便后续快速定位和计算:
- 用一个列表存储每个缓冲区的关键信息:起始地址、长度,以及它在逻辑连续空间中的偏移量(用来快速对应逻辑位置和物理缓冲区)
- 同时维护一个总长度变量,记录所有缓冲区的总字节数,用来快速判断是否能满足
get_buffer的请求
用C++的话,内部可以这么定义:
private: struct BufferChunk { uint8_t* ptr; size_t length; size_t logical_offset; }; std::vector<BufferChunk> chunks_; size_t total_size_ = 0; std::mutex mutex_; // 可选,多线程场景加锁用
add_buffer的实现逻辑
这个函数的作用就是把新的物理缓冲区“注册”到我们的虚拟缓冲区里,逻辑很直接:
- 先做合法性检查(比如空指针、长度为0的情况,根据需求加错误处理)
- 给新缓冲区分配逻辑偏移量(就是当前的总长度,这样逻辑缓冲区就是按添加顺序拼接起来的)
- 把元数据加入列表,更新总长度
代码示例:
void add_buffer(uint8_t* buffer, size_t buffer_length) { std::lock_guard<std::mutex> lock(mutex_); // 多线程场景加锁 if (buffer == nullptr || buffer_length == 0) { // 这里可以抛异常、返回错误码,或者直接忽略,看你的需求 return; } chunks_.push_back({buffer, buffer_length, total_size_}); total_size_ += buffer_length; }
get_buffer的核心实现
这个函数是整个结构的关键——要让用户拿到连续的内存地址,不管物理上是不是跨了多个缓冲区。分三种情况处理:
情况1:单个物理缓冲区就能满足需求
遍历所有已注册的缓冲区,如果有某个缓冲区的长度大于等于请求的buffer_size,直接返回它的起始地址,同时把allocated设为false(告诉用户不用自己释放这块内存)。
情况2:需要跨多个物理缓冲区,但总长度足够
如果没有单个缓冲区能容纳请求的大小,但总长度够,那就临时分配一块连续内存,把虚拟缓冲区对应位置的数据(默认从起始位置开始,要是需要支持任意偏移,可以给API加个offset参数)拷贝进去,返回这块临时内存的地址,把allocated设为true(提醒用户用完要自己释放)。
情况3:总长度不够,直接返回空指针
如果请求的大小超过了所有缓冲区的总长度,直接返回nullptr,allocated设为false。
代码示例:
uint8_t* get_buffer(size_t buffer_size, bool& allocated) { std::lock_guard<std::mutex> lock(mutex_); allocated = false; if (buffer_size == 0 || buffer_size > total_size_) { return nullptr; } // 先找能直接容纳的单个缓冲区 for (const auto& chunk : chunks_) { if (chunk.length >= buffer_size) { return chunk.ptr; } } // 没有单个缓冲区能容纳,分配临时内存并拷贝数据 uint8_t* temp_buf = new (std::nothrow) uint8_t[buffer_size]; if (temp_buf == nullptr) { return nullptr; } allocated = true; size_t copied_bytes = 0; uint8_t* dest_ptr = temp_buf; for (const auto& chunk : chunks_) { if (copied_bytes >= buffer_size) { break; } size_t copy_len = std::min(chunk.length, buffer_size - copied_bytes); memcpy(dest_ptr, chunk.ptr, copy_len); dest_ptr += copy_len; copied_bytes += copy_len; } return temp_buf; }
额外的扩展与注意事项
- 内存泄漏防范:当
allocated为true时,用户必须记得用delete[]释放返回的临时内存(如果用malloc分配就用free),不然会造成内存泄漏。要是想更安全,可以用智能指针封装,但用户给定的API是返回裸指针,所以只能靠文档提醒。 - 写入场景支持:上面的实现默认是读取场景,如果用户要通过返回的连续地址写入数据,还需要加一个同步函数,把临时内存的数据写回各个物理缓冲区:
void sync_to_chunks(uint8_t* temp_buf, size_t buffer_size) { std::lock_guard<std::mutex> lock(mutex_); if (temp_buf == nullptr || buffer_size == 0) { return; } size_t copied_bytes = 0; uint8_t* src_ptr = temp_buf; for (auto& chunk : chunks_) { if (copied_bytes >= buffer_size) { break; } size_t copy_len = std::min(chunk.length, buffer_size - copied_bytes); memcpy(chunk.ptr, src_ptr, copy_len); src_ptr += copy_len; copied_bytes += copy_len; } } - 线程安全:如果你的结构要在多线程环境下使用,一定要加锁保护
chunks_和total_size_的读写,不然会出现数据竞争。 - 缓冲区有效性:要确保通过
add_buffer添加的缓冲区,在整个数据结构的生命周期内都是有效的。如果缓冲区可能被提前释放,得考虑加引用计数或者其他内存管理机制。
内容的提问来源于stack exchange,提问作者s4eed
相关产品推荐
相关产品推荐

