【问题标题】:Can open small ASCII file, but not large binary file?可以打开小的 ASCII 文件,但不能打开大的二进制文件?
【发布时间】:2017-02-02 07:52:18
【问题描述】:

我正在使用以下代码在 Windows 上的 MSVC 中打开一个大 (5.1GB) 二进制文件。这台机器有足够的内存。问题是长度被检索为零。但是,当我将 file_path 更改为较小的 ASCII 文件时,代码可以正常工作。

为什么我无法加载大型二进制文件?我更喜欢这种方法,因为我想要一个指向文件内容的指针。

FILE * pFile;
uint64_t lSize;
char * buffer;
size_t result;

pFile = fopen(file_path, "rb");
if (pFile == NULL) { 
    fputs("File error", stderr); exit(1); 
}

// obtain file size:
fseek(pFile, 0, SEEK_END);
lSize = ftell(pFile);                                // RETURNS ZERO
rewind(pFile);

// allocate memory to contain the whole file:
buffer = (char*)malloc(sizeof(char)*lSize);
if (buffer == NULL) {
    fputs("Memory error", stderr); exit(2); 
}

// copy the file into the buffer:
result = fread(buffer, 1, lSize, pFile);             // RETURNS ZERO TOO
if (result != lSize) {                               // THIS FAILS
    fputs("Reading error", stderr); exit(3); 
}

/* the whole file is now loaded in the memory buffer. */

它不是文件权限或任何东西,它们很好。

【问题讨论】:

  • 如果ftell()返回的值是一个4字节的整数,你的long也是如此,你如何期望这个值代表任何大于2GB的数字?请参阅您的 Microsoft 文档,了解使用大小超过 2 GB 的文件的正确方法。
  • 使用second one
  • @mezamorphic 尚未注意到,但您假设您对fseek() 的调用有效。 FWIW,fseek()/ftell() 不是获取文件大小的符合 C 标准的方法。二进制文件不需要支持fseek(..., SEEK_END);,并且ftell() 为文本文件返回的值不是有意义的字节数。
  • 又是一个很好的例子,表明人们应该总是测试相关函数调用的结果。 (至少在这里fseek())。
  • 对于非常大的文件,你想用_ftelli64替换ftell,返回一个64位类型

标签: c++ c file c++11 large-files


【解决方案1】:

数据类型 long 太小,无法代表您的文件大小。使用 stat() 方法(或特定于 Windows 的替代方法 GetFileAttributes)来读取文件大小。

【讨论】:

  • 我真的很想知道使用fseek()/ftell() 来获取文件大小的想法来自哪里。它不可移植,并且不适用于 Windows 上的大文件。然而它不断出现......
  • @AndrewHenle 如果您能告诉我读取大型二进制文件的正确方法,以便我有一个指向内容的 char* 指针,其他人可以避免这些不良做法。
  • @mezamorphic 没有“读取二进制文件的正确方法”之类的东西。文件中的数据是什么?它是如何存储的?你想用它做什么?我感到遗憾的是,由于某种原因fseek()/ftell() 被教导为一种查找文件大小的方法,而实际上它不是可移植的。在一个非常流行的大文件平台上,它甚至根本不起作用。
【解决方案2】:

如果您分配 5,1 GB,最好确保您已将代码编译为 64 位并在 64 位 Windows 版本上运行它。另外,内存 address space is limited 在 32 位 Windows 和 4 GB with 32 bits code on a 64 bits Windows 上最大为 3 GB。

顺便说一句,ftell() 返回一个签名的long。您必须检查这里没有错误(例如,如果操作系统允许更大的文件大小,则溢出),因此该值不是-1。

编辑:

请注意,with MSVC, long will currently be 是 32 位数字,即使编译为 64 位也是如此。这意味着如果文件大小低于 2GB(因为标志),ftell() 将为您提供有意义的结果。

您可以使用非便携式操作系统特定的 WinAPI 函数GetFileSizeEx() 来获取带符号的 64 位数字的大文件的大小。

malloc() 采用size_t,即unsigned 64 bit number。所以在这一边你是安全的。

另一种方法是使用file mapping

第二次修改

我查看了您对尺寸收到的价值所做的编辑,这与我的预期不同。我可以在我的系统上重现该错误,并得到一个不为空的大小,但它是一个比文件大得多的数字。

查看this CERT security recommendation,似乎标准为fseek()SEEK_END 提供的保证是不充分的,这使得这是一种非常不安全的方法。

让我们重复一遍:获取大小的最安全方法是使用本机操作系统功能,即 Windows 上的 GetFileSizeEx()。在 64 位 Windows 上有一个解决方法:使用 _fseeki64()_ftelli64()

...
if (_fseeki64(pFile, 0, SEEK_END)) {
    fputs("File seek error", stderr); 
    return (1);
}
lSize = _ftelli64(pFile);                            // RETURNS EXACT SIZE
...

这很好用(最初的问题似乎与不够大的返回类型有关)。但是请记住,这是一种解决方法,我担心可能存在其他错误情况可能导致 CERT 报告的漏洞。

【讨论】:

  • 这是 Windows 7 64,我有 32GB 的内存。我之前使用过boost内存映射打开了这个文件,但我现在不想使用这种方法。
  • @mezamorphic 但是您是否编译了 64 位代码(即 x64 作为 MSVC 配置管理器中的目标 - 这与您正在编译的操作系统无关)?
  • @BoPersson 真的!我错误地认为它是无符号的。我相应地进行了编辑。
  • 没有必要拥有一个 64 位的可执行文件来使用 64 位整数值(如文件大小和偏移量)。也没有必要分配超过 2GB 的内存,除非你想一次加载/映射整个文件(这可能是个坏主意)。
  • @mezamorphic 我用函数返回的值查看了您的编辑:我可以重现错误并进行了应该解决您的问题的编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-21
  • 2011-10-31
  • 2014-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多