如何在unique_ptr字节数组中访问指定范围的字节?
问题解决与优化建议
更新:改用新读取方法(见下文)
原问题背景
刚接触C++,有少量Unreal开发经验,正在编写独立文件解析器。需求是从unique_ptr管理的字节数组中,从未知起始位置读取未知长度的字节,同时希望优化代码的整洁度与执行效率。
当前已实现文件读取函数,将文件内容存入unique_ptr<std::byte[]>并返回给fileData;同时编写了从二进制字节转换为uint32_t的函数,但逐个字节移位的方式效率较低;最后在读取压缩字节段时,memcpy调用失败,卡在相关代码行。
当前代码
文件读取函数
auto ReadFileData(std::string path) { auto fileSize = std::filesystem::file_size(path); auto buf = std::make_unique<std::byte[]>(fileSize); std::basic_ifstream<std::byte> ifs(path, std::ios::binary); ifs.read(buf.get(), fileSize); return buf; }
uint32_t解析函数
uint32_t ParseUint32(auto& fileData, uint32_t dataLocation) { return uint32_t((unsigned char)(fileData[dataLocation+3]) << 24 | (unsigned char)(fileData[dataLocation + 2]) << 16 | (unsigned char)(fileData[dataLocation + 1]) << 8 | (unsigned char)(fileData[dataLocation])); }
出错的压缩段读取代码
for(uint16_t i = 0; i<tileOffsetCount; i++) { tileOffsets[i] = ParseUint32(fileData,tileOffset + (i*4)); uint32_t tileByteSize = ParseUint32(fileData,tileByteCountsOffset +(i*4)) ; char* tileData = new char[tileByteSize]; std::memcpy(tileData, fileData+tileOffsets[i],tileByteSize); // 此行报错 }
问题解决与优化方案
1. 修复memcpy调用失败问题
std::unique_ptr<std::byte[]>不支持直接用operator+做指针偏移,必须先通过get()获取底层指针再进行偏移。同时建议用智能指针管理tileData避免内存泄漏:
for(uint16_t i = 0; i<tileOffsetCount; i++) { tileOffsets[i] = ParseUint32(fileData,tileOffset + (i*4)); uint32_t tileByteSize = ParseUint32(fileData,tileByteCountsOffset +(i*4)); // 用unique_ptr自动管理内存,避免手动delete auto tileData = std::make_unique<char[]>(tileByteSize); // 获取底层指针后再偏移,同时确保类型兼容 std::memcpy(tileData.get(), fileData.get() + tileOffsets[i], tileByteSize); // 后续处理tileData... }
2. 优化ParseUint32的执行效率
逐个字节移位拼接的方式效率较低,可直接读取4字节内存并处理字节序,推荐使用C++20的std::bit_cast(类型安全),或结合字节序判断实现跨平台兼容:
方案1:C++20+ 类型安全实现(小端转主机字节序)
#include <bit> uint32_t ParseUint32(const std::byte* data) { uint32_t value; std::memcpy(&value, data, sizeof(value)); // 假设原始数据是小端字节序,转换为主机字节序 if constexpr (std::endian::native == std::endian::big) { value = std::byteswap(value); } return value; } // 调用方式:ParseUint32(fileData.get() + dataLocation)
方案2:兼容旧标准的实现
#include <cstring> uint32_t ParseUint32(const std::byte* data) { uint32_t value; std::memcpy(&value, data, sizeof(value)); // 手动转换小端到主机字节序(如果主机是大端) #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ value = ((value & 0x000000FF) << 24) | ((value & 0x0000FF00) << 8) | ((value & 0x00FF0000) >> 8) | ((value & 0xFF000000) >> 24); #endif return value; }
3. 代码整洁度优化建议
- 增加错误处理:
ReadFileData中未处理文件不存在、读取失败等情况,需补充检查:std::unique_ptr<std::byte[]> ReadFileData(const std::string& path) { std::error_code ec; const auto fileSize = std::filesystem::file_size(path, ec); if (ec) { // 处理文件大小获取失败,比如返回空指针或抛出异常 return nullptr; } auto buf = std::make_unique<std::byte[]>(fileSize); std::basic_ifstream<std::byte> ifs(path, std::ios::binary); if (!ifs) { // 处理文件打开失败 return nullptr; } ifs.read(buf.get(), fileSize); if (ifs.gcount() != fileSize) { // 处理读取不完整的情况 return nullptr; } return buf; } - 使用
std::span简化数组操作:用std::span<const std::byte>代替直接传递unique_ptr,让函数更通用且安全:uint32_t ParseUint32(std::span<const std::byte> data, size_t offset) { // 先检查offset+4是否超出data范围,避免越界 if (offset + sizeof(uint32_t) > data.size()) { // 处理越界错误 throw std::out_of_range("ParseUint32: offset out of bounds"); } return ParseUint32(data.data() + offset); // 复用上面的优化函数 } - 避免裸数组,用
std::vector管理动态数组:比如tileOffsets建议用std::vector<uint32_t>代替固定大小数组,自动管理内存并避免越界:std::vector<uint32_t> tileOffsets(tileOffsetCount);
内容的提问来源于stack exchange,提问作者r3dstar
相关产品推荐
相关产品推荐

