C++从字节缓冲区读取无符号整数时高位丢失的问题及优化
中断编程20余年后回归,开始学习C++(此前几乎未使用过该语言,曾是Object Pascal开发者),目前是C++纯新手。尝试编写程序将整个文件读取到字节流中,再从中读取2字节或4字节的无符号小端序数值,遇到了结果不符合预期的问题。
全局变量定义
std::string File_Content = ""; // 存储整个文件内容的字节数组 unsigned __int32 File_Pos = 0; // File_Content中的当前位置,下一个要读取的字节位置
最初的读取函数实现
通过引用返回值避免无意义拷贝:
static __int8 Read2Bytes(unsigned __int32 iPos, unsigned __int8 iEndian, unsigned __int16 &iReturn) { if (iEndian == 0) // 小端序 { // 将第二个字节移到高位 iReturn = File_Content[iPos] | (File_Content[iPos + 1] << 8); } // end if (iEndian == 0) else { // 大端序,暂时不重要 } // end else (iEndian == 0) File_Pos += 2; // 移动位置标记 return 0; } // end Read2Bytes
函数调用方式
unsigned __int16 iTimeStamp = 0; // 2字节变量 Read2Bytes(File_Pos, 0, iTimeStamp); // 参数:文件流位置、字节序、返回值引用
问题现象
根据WinHex显示,字节流中的目标字节为0xC6 0x9D,预期小端序转换后得到0x9DC6,但实际返回0xFFC6,读取4字节的函数也存在相同问题。
临时变量修复后的代码
改用临时变量存储字节后组合,问题解决:
static __int8 Read2Bytes(unsigned __int32 iPos, unsigned __int8 iEndian, unsigned __int16 &iReturn) { unsigned __int8 bHiByte, bLoByte; // 分别存储两个字节 if (iEndian == 0) // 小端序 { // 原错误版本 //iReturn = File_Content[iPos] | (File_Content[iPos + 1] << 8); // 新尝试:逐字节读取后组合 bLoByte = File_Content[iPos]; // 读取第一个字节 bHiByte = File_Content[iPos + 1]; // 读取第二个字节 iReturn = (bHiByte << 8) | bLoByte; // 按字节序组合 } // end if (iEndian == 0) // 其余代码不变 }
错误原因分析
问题核心在于std::string的元素类型:std::string的operator[]返回的是char类型,而多数系统中默认char是有符号类型。
当读取值为0x9D的字节时,作为有符号char会被解释为-99(0x9D是负数的补码表示)。执行File_Content[iPos + 1] << 8时,该有符号char会被整数提升为int类型,提升过程中会进行符号扩展——高位填充1,导致0x9D变成0xFFFFFF9D。左移8位后得到0xFFFF9D00,再和0xC6按位或,最终结果为0xFFFF9DC6,而iReturn是unsigned __int16,截断后就变成了0xFFC6,与实际看到的结果一致。
用unsigned __int8临时变量存储时,赋值过程会把有符号char的符号位当作普通位处理,避免了符号扩展,因此结果正确。
高效实现方案(无需临时变量)
只需要在读取std::string元素时,显式转换为unsigned char,就能阻止符号扩展:
static __int8 Read2Bytes(unsigned __int32 iPos, unsigned __int8 iEndian, unsigned __int16 &iReturn) { if (iEndian == 0) // 小端序 { iReturn = static_cast<unsigned char>(File_Content[iPos]) | (static_cast<unsigned char>(File_Content[iPos + 1]) << 8); } else { // 大端序处理(如果需要) iReturn = (static_cast<unsigned char>(File_Content[iPos]) << 8) | static_cast<unsigned char>(File_Content[iPos + 1]); } File_Pos += 2; return 0; }
更优的字节流存储方式
直接用std::vector<unsigned char>存储文件内容,从根源上避免char的符号问题,同时语义更清晰(明确是字节流而非字符串):
#include <fstream> #include <vector> std::vector<unsigned char> File_Content; unsigned __int32 File_Pos = 0; // 读取文件到vector的示例代码 void LoadFile(const std::string& filename) { std::ifstream file(filename, std::ios::binary | std::ios::ate); if (!file) return; std::streamsize size = file.tellg(); file.seekg(0, std::ios::beg); File_Content.resize(size); file.read(reinterpret_cast<char*>(File_Content.data()), size); } // 简化后的读取函数,无需类型转换 static __int8 Read2Bytes(unsigned __int32 iPos, unsigned __int8 iEndian, unsigned __int16 &iReturn) { if (iEndian == 0) { iReturn = File_Content[iPos] | (File_Content[iPos + 1] << 8); } else { iReturn = (File_Content[iPos] << 8) | File_Content[iPos + 1]; } File_Pos += 2; return 0; }
内容的提问来源于stack exchange,提问作者AlexisVanWien

