Fwrite不会复制二进制文件副本中的所有字节

时间:2019-05-04 19:26:05

标签: c file binaryfiles fwrite fread

我正在尝试将二进制文件从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);
}

2 个答案:

答案 0 :(得分: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,并对要读取或写入的字节数进行计数。没有数学上的错误,您可以确切地说出读/写了多少字节。

这是您原始功能的一个版本,我已对其进行了更正和注释,因此在没有任何问题的情况下它可以正常工作。

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);
    }
}

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

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

操作文件是您要 非常 要小心的一件事,因为如果您的代码不起作用,则可能会丢失数据,因此在真正使用它之前文件,以确保测试文件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上,您可以在文本模式下遇到很多陷阱。如果可以的话,最好完全避免这些情况。

祝你好运!

答案 1 :(得分:1)

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

如果BYTE_SIZE4,如类型int* buf = 0;所示,并且如果源文件的字节数比4的倍数多3个字节,则将对您的观察结果进行详细说明。

您可以通过将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;
}