fwrite 无法写入完整(非常大)的缓冲区

MrP*_*ler 5 c fwrite

我有一个非常大的 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。

其他人可以复制这个吗?

And*_*zel 5

问题似乎出在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_SIZE4295000000LL(略高于 4 GiB)时,则会fwrite出现与问题中描述的相同的行为。但是,当我将其设置为4293000000LL(略低于 4 GiB)时,它就可以工作了。

如果我使用LoadLibraryonucrtbase.dll而不是msvcrt.dll,那么它在两种情况下都有效。

我不知道有什么方法可以让 Mingw-w64 使用该库uartbase.dll而不是msvcrt.dll. 因此,如果你想改变它,恐怕你必须编辑 Mingw-w64 的源代码。