【问题标题】:Fwrite does not copy all bytes in binary file copyfwrite 不会复制二进制文件副本中的所有字节
【发布时间】:2019-09-22 23:04:22
【问题描述】:

我正在尝试将二进制文件从 src 复制到 dst。该脚本似乎复制了所有字节。但是当我在 Hex Workshop 中打开这两个文件时,我看到 dst 文件在文件末尾总是缺少 3 个字节。这3个字节应该是00 00 00,这个问题让我无法打开dst文件。

void binaryCopy(char **argv) {
    int *buf = 0;
    int elements = 0;
    int size = 0, wantOverwrite = 0;
    FILE *src = fopen(argv[SRC_POS], "rb");
    FILE *dst = fopen(argv[DST_POS], "w+b");
    if (src) {
        if (dst) {
            wantOverwrite = overwrite();
        }
        if (wantOverwrite) {
            fseek(src, 0L, SEEK_END);
            size = ftell(src);
            fseek(src, 0L, SEEK_SET);
            buf = (int *)malloc(size);
            elements = fread(buf, BYTE_SIZE, size / BYTE_SIZE, src);
            fwrite(buf, BYTE_SIZE, elements, dst);
            printf("copy completed");
            free(buf);
        }
    }
    fclose(dst);
    fclose(src);
}

【问题讨论】:

  • 如果这是minimal reproducible example,我们可以自己尝试,但不知道你所有的常量是什么以及你没有显示的函数是什么,很难说。为什么你不只是阅读和写作size?当size 不是BYTE_SIZE 的偶数倍时会发生什么?
  • 我注意到的第一件事是完全没有错误检查。你不会走得太远。也就是说,欢迎来到 Stack Overflow!请收下tour 并阅读How to Ask
  • 如果你在 fseek 调用中需要 0L 而不是 0,那么你做错了,
  • 发生了奇怪的事情,但您的src 文件0xEF,0xBB,0xBF 中的前三个字节是?根据该文件的准备/附加方式等,如果您认为它包含所有整数,那么您总是少 3 个字节,请在开头检查字节顺序标记。
  • @James:您可以通过点击分数下方的灰色复选标记来接受其中一个答案

标签: c file binaryfiles fwrite fread


【解决方案1】:

你写的函数有几个问题。

  • fopen(dstFilename, "w+b"); 会截断文件,因此您以后的覆盖检查毫无意义。
  • 您没有在 malloc 之后检查 NULL,并且您的缓冲区应该是 unsigned char*,因为这是 fread/fwrite 将其解释为的内容。
  • 最后,两个fclose 函数都可以使用NULL 文件指针调用,这可能会导致崩溃。您应该将它们移动到您知道每个都已成功打开的范围内。
  • 引发这个问题的最大问题是,您没有处理文件大小不是BYTE_SIZE 的偶数倍的情况。由于您为整个文件分配了足够的内存,因此您应该只读取和写入整个文件。 fread(buf, 1, size, src);fwrite(buf, 1, size, dst);。一般来说,最好将fread/fwrite 的元素大小参数设置为 1,并将 count 设置为要读取或写入的字节数。没有数学错误,您可以准确判断读取/写入了多少字节。

这是您的原始函数的一个版本,我已对其进行了更正和注释,因此如果没有任何问题,它就可以工作。

void originalBinaryCopy(const char *srcFilename, const char *dstFilename)
{
    //odd size to ensure remainder
    const size_t BYTE_SIZE = 777;

    int *buf = 0;
    int elements = 0;
    int size = 0, wantOverwrite = 0;
    FILE *src = fopen(srcFilename, "rb");
    //This truncates dst, so the overwirte check is meaningless
    FILE *dst = fopen(dstFilename, "w+b");
    if (src)
    {
        if (dst)
        {
            fseek(src, 0L, SEEK_END);
            size = ftell(src);
            fseek(src, 0L, SEEK_SET);
            //always check for NULL after malloc - This should be a char*
            buf = (int *)malloc(size);
            if (!buf)
            {
                fclose(dst);
                fclose(src);
                return;
            }
            elements = fread(buf, BYTE_SIZE, size / BYTE_SIZE, src);
            fwrite(buf, BYTE_SIZE, elements, dst);

            //added, copy remainder
            elements = fread(buf, 1, size % BYTE_SIZE, src);
            fwrite(buf, 1, size % BYTE_SIZE, dst);
            //end

            printf("copy completed %s -> %s\n", srcFilename, dstFilename);
            free(buf);
        }
    }
    //dst could be NULL here, move inside if(dst) scope above
    fclose(dst);
    //src could be NULL here, move inside if(src) scope above
    fclose(src);

    if (comp(srcFilename, dstFilename) != 0)
    {
        printf("compare failed - %s -> %s\n", srcFilename, dstFilename);
    }
}

注意最后是如何处理剩余部分的。

以下是我将如何处理复制文件以及测试套件以创建、复制和验证一组文件。它显示了如果您不想截断目标,如何避免截断目标,并且在实际函数中有相当多的错误检查。我没有在调用方进行任何特定的错误检查,但对于真正的代码,我会枚举所有可能的错误并使用这些返回值传递给错误处理函数,该函数可以将它们打印出来并可能退出程序。

操作文件是您希望非常小心的一件事,因为如果您的代码不起作用,则可能会丢失数据,因此在将其用于真实的文件确保它与测试文件 100% 可靠。

#include <malloc.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define TEST_FILE_MIN 1024
#define TEST_FILE_MAX 1024 * 1024
const char *src_pattern = "src_file_%08x.bin";
const char *dst_pattern = "dst_file_%08x.bin";

void createTestFiles(const char *pattern)
{
    char filename[256] = { 0 };
    char buffer[1024];

    for (size_t i = 0; i < sizeof(buffer); ++i)
    {
        buffer[i] = rand();
    }

    for (size_t i = TEST_FILE_MIN; i <= TEST_FILE_MAX; i *= 2)
    {
        sprintf(filename, pattern, i);
        FILE *dst = fopen(filename, "wb");
        if (dst)
        {
            size_t reps = i / TEST_FILE_MIN;
            for (size_t w = 0; w < reps; ++w)
            {
                fwrite(buffer, 1, sizeof(buffer), dst);
            }
            fclose(dst);
        }
    }
}

int comp(const char *srcFilename, const char *dstFilename)
{
    FILE *src = fopen(srcFilename, "rb");
    if (!src)
    {
        return -1;
    }
    //open for reading to check for existence
    FILE *dst = fopen(dstFilename, "rb");
    if (!dst)
    {
        fclose(src);
        return -2;
    }

    fseek(src, 0, SEEK_END);
    size_t srcSize = ftell(src);
    fseek(src, 0, SEEK_SET);

    fseek(dst, 0, SEEK_END);
    size_t dstSize = ftell(dst);
    fseek(dst, 0, SEEK_SET);

    if (srcSize == 0 || dstSize == 0 || srcSize != dstSize)
    {
        fclose(src);
        fclose(dst);
        return -3;
    }

    unsigned char *srcBuf = (unsigned char *)calloc(1, srcSize);
    unsigned char *dstBuf = (unsigned char *)calloc(1, srcSize);
    if (!srcBuf || !dstBuf)
    {
        fclose(src);
        fclose(dst);
        return -4;
    }

    if (fread(srcBuf, 1, srcSize, src) != srcSize)
    {
        fclose(src);
        fclose(dst);
        return -5;
    }
    if (fread(dstBuf, 1, dstSize, dst) != dstSize)
    {
        fclose(src);
        fclose(dst);
        return -6;
    }
    fclose(src);
    fclose(dst);

    //result * 100 to make this error outside te range of the other general errors from this function.
    int result = memcmp(srcBuf, dstBuf, srcSize) * 100;
    free(srcBuf);
    free(dstBuf);

    return result;
}

void originalBinaryCopy(const char *srcFilename, const char *dstFilename)
{
    //odd size to ensure remainder
    const size_t BYTE_SIZE = 777;

    int *buf = 0;
    int elements = 0;
    int size = 0, wantOverwrite = 0;
    FILE *src = fopen(srcFilename, "rb");
    //This truncates dst, so the overwirte check is meaningless
    FILE *dst = fopen(dstFilename, "w+b");
    if (src)
    {
        if (dst)
        {
            fseek(src, 0L, SEEK_END);
            size = ftell(src);
            fseek(src, 0L, SEEK_SET);
            //always check for NULL after malloc - This should be a char*
            buf = (int *)malloc(size);
            if (!buf)
            {
                fclose(dst);
                fclose(src);
                return;
            }
            elements = fread(buf, BYTE_SIZE, size / BYTE_SIZE, src);
            fwrite(buf, BYTE_SIZE, elements, dst);

            //added, copy remainder
            elements = fread(buf, 1, size % BYTE_SIZE, src);
            fwrite(buf, 1, size % BYTE_SIZE, dst);
            //end

            printf("copy completed %s -> %s\n", srcFilename, dstFilename);
            free(buf);
        }
    }
    //dst could be NULL here, move inside if(dst) scope above
    fclose(dst);
    //src could be NULL here, move inside if(src) scope above
    fclose(src);

    if (comp(srcFilename, dstFilename) != 0)
    {
        printf("compare failed - %s -> %s\n", srcFilename, dstFilename);
    }
}

int binaryCopy(const char *srcFilename, const char *dstFilename, bool overwrite)
{
    //arbitrary odd size so we can make sure we handle a partial buffer.
    //assuming the code tests successfully I'd use something like 64 * 1024.
    unsigned char buffer[7777] = { 0 };

    FILE *src = fopen(srcFilename, "rb");
    if (!src)
    {
        //Error, source file could not be opened
        return -1;
    }
    //open for reading to check for existence
    FILE *dst = fopen(dstFilename, "rb");
    if (dst)
    {
        if (!overwrite)
        {
            //Error, dest file exists and we can't overwrite it
            fclose(src);
            fclose(dst);
            return -2;
        }

        //reopen dst it for writing
        if (!freopen(dstFilename, "wb", dst))
        {
            fclose(src);
            fclose(dst);
            dst = NULL;
        }
    }
    else
    {
        //it didn't exist, create it.
        dst = fopen(dstFilename, "wb");
    }

    if (!dst)
    {
        //Error, dest file couldn't be opened
        fclose(src);
        return -3;
    }

    //Get the size of the source file for comparison with what we read and write.
    fseek(src, 0, SEEK_END);
    size_t srcSize = ftell(src);
    fseek(src, 0, SEEK_SET);

    size_t totalRead = 0;
    size_t totalWritten = 0;

    size_t bytesRead = 0;
    while (bytesRead = fread(buffer, 1, sizeof(buffer), src))
    {
        totalRead += bytesRead;
        totalWritten += fwrite(buffer, 1, bytesRead, dst);
    }
    fclose(dst);
    fclose(src);

    if (totalRead != srcSize)
    {
        //src read error
        return -4;
    }
    if (totalWritten != srcSize)
    {
        //dst write error
        return -5;
    }
    return 0;
}

int main()
{
    srand((unsigned)time(0));

    createTestFiles(src_pattern);

    for (size_t i = TEST_FILE_MIN; i <= TEST_FILE_MAX; i *= 2)
    {
        char srcName[256];
        char dstName[256];
        sprintf(srcName, src_pattern, i);
        sprintf(dstName, dst_pattern, i);

        //use my copy to create dest file
        if (binaryCopy(srcName, dstName, true) != 0)
        {
            printf("File: '%s' failed initial copy.", srcName);
        }

        originalBinaryCopy(srcName, dstName);

        if (binaryCopy(srcName, dstName, true) != 0)
        {
            printf("File: '%s' failed overwrite copy.", srcName);
        }
        if (binaryCopy(srcName, dstName, false) == 0)
        {
            printf("File: '%s' succeeded when file exists and overwrite was not set.", srcName);
        }
        //If compare succeeds delete the files, otherwise leave them for external comparison and print an error.
        if (comp(srcName, dstName) == 0)
        {
            if (remove(srcName) != 0)
            {
                perror("Could not remove src.");
            }
            if (remove(dstName) != 0)
            {
                perror("Could not remove dst.");
            }
        }
        else
        {
            printf("File: '%s' did not compare equal to '%s'.", srcName, dstName);
        }
    }
    return 0;
}

希望这能给您带来一些尝试,以确保您的复印机尽可能好。另外值得注意的是,我不会区分复制文本/二进制文件。文件就是文件,如果您的目标是复制它们,那么您应该始终以二进制模式进行操作,以便副本是相同的。在 Windows 以外的操作系统上,这无关紧要,但在 Windows 上,您可以在文本模式下遇到许多陷阱。如果可以的话,最好完全避免这些。

祝你好运!

【讨论】:

    【解决方案2】:

    您观察到的最可能原因是文件大小不是BYTE_SIZE 的倍数:fread(buf, BYTE_SIZE, size / BYTE_SIZE , src); 读取的是BYTE_SIZE 的倍数,fwrite 调用写入读取的字节数。

    如果BYTE_SIZE4,正如int* buf = 0; 类型所表明的那样,并且如果源文件的字节数多于4 的倍数,那么您的观察结果将得到充分解释。

    您可以通过将buf 设置为unsigned char * 并将代码更改为:

            elements = fread(buf, 1, size , src);
            fwrite(buf, 1, elements, dst);
    

    另请注意,无需在更新模式下打开文件(模式字符串中的 +)、错误且未显式处理且 fclose() 调用放错了位置。

    如果overwrite() 返回 0,截断目标文件似乎也不正确。

    这是一个更正的版本,具有更好的错误处理:

    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int binaryCopy(char *argv[]) {
        FILE *src, *dst;
        long file_size;
        size_t size, size_read, size_written;
        int wantOverwrite;
        unsigned char *buf;
    
        if ((src = fopen(argv[SRC_POS], "rb")) == NULL) {
            printf("cannot open input file %s: %s\n", argv[SRC_POS], strerror(errno));
            return -1;
        }
        wantOverwrite = overwrite();
        if (!wantOverwrite) {
            fclose(src);
            return 0;
        }
        if ((dst = fopen(argv[DST_POS], "wb")) == NULL) {
            printf("cannot open output file %s: %s\n", argv[DST_POS], strerror(errno));
            fclose(src);
            return -1;
        }
        fseek(src, 0L, SEEK_END);
        file_size = ftell(src);
        fseek(src, 0L, SEEK_SET);
        size = (size_t)file_size;
        if ((long)size != file_size) {
            printf("file size too large for a single block: %ld\n", file_size);
            fclose(src);
            fclose(dst);
            return -1;
        }
        buf = malloc(size);
        if (buf == NULL) {
            printf("cannot allocate block of %zu bytes\n", size);
            fclose(src);
            fclose(dst);
            return -1;
        }
        size_read = fread(buf, 1, size, src);
        if (size_read != size) {
            printf("read error: %zu bytes read out of %zu\n", size_read, size);
        }
        size_written = fwrite(buf, 1, size_read, dst);
        if (size_written != size_read) {
            printf("write error: %zu bytes written out of %zu\n", size_written, size_read);
        }
        if (size_written == size) {
            printf("copy completed\n");
        }
        free(buf);
        fclose(dst);
        fclose(src);
        return 0;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-17
      • 1970-01-01
      • 2018-12-11
      • 1970-01-01
      相关资源
      最近更新 更多