我有一个非常大的 8GB 数组,uint64_t需要写入文件。由于某种原因,fwrite()只写入 290MB(正好 305,131,520 字节),然后就停止了。我的程序正在使用 CPU 的完整核心(此时它唯一的工作是写入文件),但任务管理器显示磁盘活动为 0。我必须按 Ctrl+C 来停止它。这是我将数组写入文件的代码:
void writeToFile(char* fileName, u64* array, size_t arraySize)
{
FILE* fp;
fp = fopen(fileName, "wb");
if (fp == NULL)
{
printf("Unable to write file");
}
fwrite(array, 8, arraySize, fp);
fclose(fp);
}
Run Code Online (Sandbox Code Playgroud)
如果我在 Visual Studio 中编译,它可以正常工作并写入整个数组。不过,我喜欢在 Visual Studio 中编写代码,然后使用 CodeBlocks 和 gcc 进行编译(gcc 具有更好的优化)。使用gcc编译时,无法写入超过290MB。这里有什么问题?
注意:我正在 Windows 上为 Windows 进行编译,并且该程序正在写入 Gen 4 NVMe SSD,因此写入时间应该小于 10 秒。我用的gcc是x86_64-w64-mingw32-gcc.exeCodeBlocks自带的。
编辑
好的,这是一个最小的可重现程序:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#define u64 uint64_t
#define i64 int64_t
int writeToFile(char* fileName, u64* array, size_t arraySize)
{
FILE* fp;
fp = fopen(fileName, "wb");
if (fp == NULL)
{
printf("Unable to write file");
return 1;
}
fwrite(array, 8, arraySize, fp);
fclose(fp);
return 0;
}
int main()
{
i64 numItems = 1111883624;
u64* array = calloc(numItems, 8);
for(i64 i = 0; i < numItems; i++)
{
array[i] = i;
}
printf("\nWriting file...\n");
char* filePath = "C:\\path\\test.bin";
int x = writeToFile(filePath, array, numItems);
if(x == 0)
{
printf("DONE");
}
else
{
printf("Save failed");
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
用于编译的命令行(根据CodeBlocks)是
x86_64-w64-mingw32-gcc.exe -Wall -O2 -fexpensive-optimizations -O3 -m64 -c "C:\path\main.c" -o obj\Release\main.o
x86_64-w64-mingw32-gcc.exe -o bin\Release\test.exe obj\Release\main.o -s -O3 -m64
Run Code Online (Sandbox Code Playgroud)
为了好玩,我一直在测试 的不同值numItems。只要恰好numItems小于536,870,912( 512 * 1024 * 1024),就没有问题。之后,它写入文件的一部分,然后似乎陷入了某个循环。因此,即使我使用针对 64 位版本的 64 位编译器,如果调用fwrite()超过 4GB 的数据,它仍然存在问题。
我认为我的过时gcc版本(包含在 CodeBlocks 中的版本)可能是问题所在,但我下载了msys2 附带的8.1.0最新版本。13.2.0问题仍然存在。
编辑2
我一直在测试 的各种值numItems,这里是结果的 csv:
void writeToFile(char* fileName, u64* array, size_t arraySize)
{
FILE* fp;
fp = fopen(fileName, "wb");
if (fp == NULL)
{
printf("Unable to write file");
}
fwrite(array, 8, arraySize, fp);
fclose(fp);
}
Run Code Online (Sandbox Code Playgroud)
放入电子表格中并按“,”拆分为列,以便更好地阅读。 fwrite()由于某种原因,内部必须使用 32 位。536871424 很有趣,因为它是 (512 * 1024 * 1024) + 512,并且它是 (512 * 1024 * 1024) 之后将任何数据写入文件的 numItems 的第一个值。它写入的4096字节恰好是正在写入的SSD的分配单元大小,但我不知道这是否是巧合。1073742336 也类似,即 (1024 * 1024 * 1024) + 512。
其他人可以复制这个吗?
问题似乎出在fwriteMicrosoft C 运行时库中函数的实现上msvcrt.dll,该库由 Mingw-w64 使用。它似乎无法处理超过 4 GiB的写入。
您的程序与 Visual Studio 配合使用的原因是您的程序的该版本没有使用msvcrt.dll,而是使用ucrtbase.dll,这是较新版本的 Microsoft C 运行时库,它似乎不存在此问题。
我能够通过使用LoadLibraryon重现您在 Visual Studio 中的问题msvcrt.dll,代码如下:
#define _CRT_SECURE_NO_WARNINGS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#define THE_SIZE 4295000000LL
//#define THE_SIZE 4293000000LL
#define LIBRARY_NAME "msvcrt.dll"
//#define LIBRARY_NAME "ucrtbase.dll"
int main( void )
{
HMODULE hLibrary;
FILE * (*my_fopen) ( const char *filename, const char *mode );
size_t (*my_fwrite)( const void *buffer, size_t item_size, size_t num_items, FILE *fp );
int (*my_fclose)( FILE *fp );
// load the specified version of the Microsoft C runtime library
hLibrary = LoadLibrary( LIBRARY_NAME );
if ( hLibrary == NULL )
{
fprintf( stderr, "Error returning value!n" );
exit( EXIT_FAILURE );
}
// get the addresses of the required functions
my_fopen = (void*)GetProcAddress( hLibrary, "fopen" );
my_fwrite = (void*)GetProcAddress( hLibrary, "fwrite" );
my_fclose = (void*)GetProcAddress( hLibrary, "fclose" );
// verify that all functions were found
if ( my_fopen == NULL || my_fwrite == NULL || my_fclose == NULL )
{
fprintf( stderr, "GetProcAddress error!\n" );
exit( EXIT_FAILURE );
}
// perform the actual test
FILE *fp;
const char *buffer = malloc( THE_SIZE );
printf( "%p\n", fp = my_fopen ( "test.bin", "wb" ) );
if ( fp != NULL )
{
printf( "%zu\n", my_fwrite( buffer, THE_SIZE, 1, fp ) );
my_fclose( fp );
}
// cleanup
FreeLibrary( hLibrary );
}
Run Code Online (Sandbox Code Playgroud)
当使用LoadLibraryonmsvcrt.dll并设置THE_SIZE为4295000000LL(略高于 4 GiB)时,则会fwrite出现与问题中描述的相同的行为。但是,当我将其设置为4293000000LL(略低于 4 GiB)时,它就可以工作了。
如果我使用LoadLibraryonucrtbase.dll而不是msvcrt.dll,那么它在两种情况下都有效。
我不知道有什么方法可以让 Mingw-w64 使用该库uartbase.dll而不是msvcrt.dll. 因此,如果你想改变它,恐怕你必须编辑 Mingw-w64 的源代码。