(1)引言
以下是QT C++读取数据文件(QDataStream)的代码:
/**
* 按双字读取
* @param fis 文件输入流
* @param isBigEndian 是否大头(字节序)
* @return 双字值
*/
DWORD FsFileUtil::readAsDword(QDataStream &fis, const bool isBigEndian) {
BYTE byte1 = -1, byte2 = -1, byte3 = -1, byte4 = -1;
DWORD val = -1;
fis >> byte1 >> byte2 >> byte3 >> byte4;
//EoF checking
if((-1 == byte4)||(-1 == byte3)||(-1 == byte2)||(-1 == byte1)) {
return (-1);
}
if(isBigEndian) {
val = ((byte1 << 24)|(byte2 << 16)|(byte3 << 8)|byte4);
} else {
val = ((byte4 << 24)|(byte3 << 16)|(byte2 << 8)|byte1);
}
return (val);
}
其意图很简单:从当前游标位置读取一个双字(4字节)数据。
稍微复杂一点的就是一个字节序的考虑。
(2)问题
该代码做UT(单元测试)时,遇到了读取无法中断(就是读起来没完没了)。以下是数据文件内容:
中断的原因就是一直没有获取到约定的 EOF(-1)。
经调试跟踪,在读取完毕后,函数的返回值还是 4,294,967,295(0xFFFFFF),没有返回 -1。
经查,QDataSteam的 >> 即便读取到文件末尾了,也不会返回 -1,而需要使用方法 atEnd 来判定。
(2.1)对策一
将判断 EOF的语句修改为:
//EoF checking
if(fis.atEnd()) {
return (-1);
}
再次调试,则判定文件末尾正常,但函数还是不会返回 -1。其原因是函数返回值类型是无符号型 DWORD。
-1 的DWORD值是0xFFFFFF。
(2.2)对策二
将函数的返回值类型扩展为带符号型 int64。即:
int64 FsFileUtil::readAsDword(QDataStream &fis, const bool isBigEndian) {
BYTE byte1 = -1, byte2 = -1, byte3 = -1, byte4 = -1;
int64 val = -1;
再次调试,遇到了读取中断的问题(就是文件还没读取完就提前中断了)。
经调试跟踪,在读取第1个 0xFFFFFF 时,每个字节的读取都正常(255),问题发生在代码的 return 语句处,返回 -1值了:
val = ((byte1 << 24)|(byte2 << 16)|(byte3 << 8)|byte4);
问题也秒了,无符号类型计算转换为带符号类型时,发生了值溢出。
(2.3)对策三
返回值val需要扩展,要不用带符号类型int64,在值计算时把值强制扩展为int64;
或者用无符号类型DWORD。即:
int64 FsFileUtil::readAsDword(QDataStream &fis, const bool isBigEndian) {
BYTE byte1 = -1, byte2 = -1, byte3 = -1, byte4 = -1;
int64 val = -1;
。。。
val = (((int64)byte1 << 24)|((int64)byte2 << 16)|((int64)byte3 << 8)|(int64)byte4);
或者:
int64 FsFileUtil::readAsDword(QDataStream &fis, const bool isBigEndian) {
BYTE byte1 = -1, byte2 = -1, byte3 = -1, byte4 = -1;
DWORD val = -1;
。。。
val = (((DWORD)byte1 << 24)|((DWORD)byte2 << 16)|((DWORD)byte3 << 8)|(DWORD)byte4);
相比之下,第1种方案的可读性要好一些。
(3)结论
数据文件读取需关注的点:
- 判定是否到达文件末尾(End of File)的方式
- 目标的值域范围(宁大勿小)
- 无符号类型与带符号类型之间的转换
(4)相关文档
- 开发笔记之:文件读取溢出分析(JAVA版)