在Windows系统中查找大于4GB的正确文件大小

7

我在Windows系统中使用Visual Studio编写了以下的C++代码,用于查找文件的大小:

(p_findFileData->nFileSizeHigh * MAXDWORD) + p_findFileData->nFileSizeLow);

如果文件大小大于4GB,这种方法无法给出正确的文件大小。经过一些研究后,我尝试了以下方法:

(p_findFileData->nFileSizeHigh * (MAXDWORD+1)) + p_findFileData->nFileSizeLow); 

据我所知,nfilesizehigh和nfilesizelow各为32位,共同构成文件大小的64位值。如果文件大小值大于32位,则我们需要将maxdword(在我的情况下为0xffffffff)乘以nfilesizehigh。但第二种解决方案也没有奏效,给出了比实际文件大小更小的结果。我又尝试了以下方法:

ULONGLONG FileSize = (FindFileData.nFileSizeHigh * 4294967296) + FindFileData.nFileSizeLow;

而且它还起作用。此外,我使用了另一种解决方案,可以获得文件大小:

 ULONGLONG FileSize = FindFileData.nFileSizeHigh;
 FileSize <<= sizeof( FindFileData.nFileSizeHigh ) *8; 
 FileSize |= FindFileData.nFileSizeLow;

上述解决方案也可行:

我想知道为什么前两个解决方案不起作用,如果可能的话,请解释一下最后一个解决方案,因为我想了解代码的内部工作原理。非常感谢您的帮助。

4个回答

12
使用ULARGE_INTEGER结构体来组合这些值,而不是手动计算/移位:
ULARGE_INTEGER ul;
ul.HighPart = p_findFileData->nFileSizeHigh;
ul.LowPart = p_findFileData->nFileSizeLow;
ULONGLONG FileSize = ul.QuadPart;

9
我会使用:

ULONGLONG FileSize = (static_cast<ULONGLONG>(FindFileData.nFileSizeHigh) <<
                      sizeof(FindFileData.nFileSizeLow) *8) |
                     FindFileData.nFileSizeLow;

将32位值转换为64位值然后再赋给FileSize变量,需要进行ULONGLONG类型的强制类型转换。

当然,您也可以使用乘法 - 但它在任何特定方面都不是“更好”的,而且很可能稍微慢一点(在这种情况下可能无关紧要),我看不出使用乘法的任何好处。

现在来看一下“不起作用”的变体:

即使这种方式可以正常工作:

 (p_findFileData->nFileSizeHigh * MAXDWORD) + p_findFileData->nFileSizeLow);

如果你使用 MAXDWORD 赋值,你将得到错误的值。因为 MAXDWORD 比 4GB 小1,所以你最终得到的数值是错误的 [也许相差不大,但至少会多1个字节,可能还要更多]。然而,由于我们正在处理32位值,实际上变成了:

 -p_findFileData->nFileSizeHigh + p_findFileData->nFileSizeLow;

因为MAXDWORD和-1相同(是的,它可能是一个无符号值,但如果您以这种方式使值溢出,它的行为与负有符号值完全相同)。

这个数学上是正确的,但由于它溢出了32位值,所以它不起作用。

 (p_findFileData->nFileSizeHigh * (MAXDWORD+1)) + p_findFileData->nFileSizeLow);

所以你得到了 低位 + (0 * 高位),显然这是不正确的。

使用类型转换,这将起作用:

 static_cast<ULONGLONG>(p_findFileData->nFileSizeHigh) * (MAXDWORD+1) +
 p_findFileData->nFileSizeLow;

嗨Mats,非常感谢您详细的回答。我很感激。此外,我有一些问题,比如:为什么在不起作用的变体中我们要将filesizehigh乘以MAXDWORD或4gb?这背后的逻辑是什么?而在第一段代码中,为什么我们要将filesizehigh乘以8? - phantomsays
1
MAXDWORD = (2^32)-1,所以离4GB只有一步之遥。 sizeof(FileSizeHigh) * 8FileSizeHigh 的位数(32)。左移(<<)与乘以2^32相同。实际上,我认为正确的操作应该是移位sizeof(FileSizeLow) * 8,因为这是低位中位数的位数 - 它也是32位,但如果有人将FileSizeLowFileSizeHigh更改为64位,则数学将继续工作[除非需要比ULONGLONG更大的存储空间]。 - Mats Petersson
我尝试过这个,但它不起作用:static_cast<ULONGLONG>(p_findFileData->nFileSizeHigh) * (MAXDWORD+1) + p_findFileData->nFileSizeLow; - phantomsays
1
你可能需要使用 (static_cast<ULONGLONG>(MAXDWORD)+1) 来避免它变成 0(这有点像汽车上的里程表,如果你已经行驶了99999英里,下一次就会翻转成00000 - 二进制中的全1也是如此)。 - Mats Petersson
昨天试过了,明白发生了什么。谢谢你的解释,马茨。 - phantomsays

3
(p_findFileData->nFileSizeHigh * MAXDWORD) + p_findFileData->nFileSizeLow);

这个结果是错误的,因为MAXDWORD的值是错误的。

(p_findFileData->nFileSizeHigh * (MAXDWORD+1)) + p_findFileData->nFileSizeLow); 

这不起作用是因为你在将类型转换为足够大的值之前,将+1添加到MAXDWORD。

(FindFileData.nFileSizeHigh * 4294967296) + FindFileData.nFileSizeLow;

这个能够工作是因为4294967296是一个64位类型。

FileSize <<= sizeof( FindFileData.nFileSizeHigh ) *8; 
FileSize |= FindFileData.nFileSizeLow;

这样做的原因是通过移位高位32位(相当于乘以4294967296),然后使用按位或“添加”低位。

谢谢您的回复,Thomas。您能否告诉我为什么在最后一段代码中我们要将filesizehigh乘以8,以及为什么在第一段代码中MAXDWORD是错误的大小?如果它是错误的大小,应该是什么?如果可能的话,请告诉我。 - phantomsays
1
MAXDWORD的值为4294967295。正确的值应为4294967296。然而,如果采用2补码表示,MAXDWORD+1的结果为0,而非4294967296。若要使结果为4294967296,则必须先将其转换为64位值。 - Thomas
2
我个人认为像这样硬编码魔数是一个不好的做法;你需要解释性注释来强调这个特定魔数的问题。在我看来,更明智(也更易懂)的做法是将FindFileData.nFileSizeHigh转换为ULONGLONG,向左移位并加上FindFileData.nFileSizeLow - Nik Bougalis
你无法避免使用魔数,无论你的魔数是8、32还是2^32都没有区别。 - Thomas
有道理...非常感谢Thomas和Nik提供的意见。 - phantomsays

1
我曾经遇到过完全相同的问题,对于我的代码来说答案非常简单。只需要将MAXDWORD强制转换为64位整数(例如uint64_t)。这背后的解释可以在我收到的答案这里中找到。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接