9

Вопрос по file-io, c – Испытанный и правда простой код копирования файлов в C?

Это выглядит как простой вопрос, но я не нашел ничего подобного здесь.

Поскольку в C нет функции копирования файлов, мы должны сами копировать файлы, но я не хотел бы заново изобретать колесо даже для таких простых вещей, как этот, поэтому я хотел бы спросить облако:

  1. What code would you recommend for file copying using fopen()/fread()/fwrite()?
    • What code would you recommend for file copying using open()/read()/write()?

Этот код должен быть переносимым (windows / mac / linux / bsd / qnx / younameit), стабильным, проверенным временем, быстрым, эффективным с точки зрения памяти и т. Д. Приветствуется проникновение во внутренние компоненты конкретной системы для повышения производительности (например, получение кластера файловой системы). размер).

Это кажется тривиальным вопросом, но, например, исходный код для команды CP не содержит 10 строк кода C.

6ответов

1

Вот очень простой и понятный пример:Скопируйте файл, Поскольку он написан на ANSI-C без каких-либо особых вызовов функций, я думаю, что этот будет в значительной степени переносимым.

1

В зависимости от того, что вы подразумеваете под копированием файла, это, безусловно, далеко не тривиально. Если вы имеете в виду только копирование контента, то тут почти нечего делать. Но обычно вам нужно копировать метаданные файла, и это, безусловно, зависит от платформы. Я не знаю ни одной библиотеки C, которая делает то, что вы хотите, переносимым способом. Простая обработка имени файла не является тривиальным вопросом, если вы заботитесь о переносимости.

В C ++ есть файловая библиотека вувеличение

1

Одна вещь, которую я обнаружил при реализации своей собственной копии файла, и это кажется очевидным, но это не так: входы / выходыslow, Вы можете в значительной степени рассчитать скорость своей копии по тому, сколько из них вы делаете. Очевидно, что вам нужно сделать как можно меньше из них.

Наилучшие результаты, которые я обнаружил, были, когда я получил себе ginourmous буфер, прочитал весь исходный файл в него за один ввод / вывод, а затем записал весь буфер обратно из него за один ввод / вывод. Если бы мне даже пришлось сделать это за 10 партий, это стало слишком медленным. Попытка прочитать и записать каждый байт, как наивный программист мог бы попытаться сначала, была просто болезненной.

5

Это функция, которую я использую, когда мне нужно скопировать из одного файла в другой - с помощью тестового жгута:

/*
@(#)File:           $RCSfile: fcopy.c,v $
@(#)Version:        $Revision: 1.11 $
@(#)Last changed:   $Date: 2008/02/11 07:28:06 $
@(#)Purpose:        Copy the rest of file1 to file2
@(#)Author:         J Leffler
@(#)Modified:       1991,1997,2000,2003,2005,2008
*/

/*TABSTOP=4*/

#include "jlss.h"
#include "stderr.h"

#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_fcopy_c[] = "@(#)$Id: fcopy.c,v 1.11 2008/02/11 07:28:06 jleffler Exp $";
#endif /* lint */

void fcopy(FILE *f1, FILE *f2)
{
    char            buffer[BUFSIZ];
    size_t          n;

    while ((n = fread(buffer, sizeof(char), sizeof(buffer), f1)) > 0)
    {
        if (fwrite(buffer, sizeof(char), n, f2) != n)
            err_syserr("write failed\n");
    }
}

#ifdef TEST

int main(int argc, char **argv)
{
    FILE *fp1;
    FILE *fp2;

    err_setarg0(argv[0]);
    if (argc != 3)
        err_usage("from to");
    if ((fp1 = fopen(argv[1], "rb")) == 0)
        err_syserr("cannot open file %s for reading\n", argv[1]);
    if ((fp2 = fopen(argv[2], "wb")) == 0)
        err_syserr("cannot open file %s for writing\n", argv[2]);
    fcopy(fp1, fp2);
    return(0);
}

#endif /* TEST */

Очевидно, что эта версия использует файловые указатели из стандартного ввода-вывода, а не файловые дескрипторы, но она достаточно эффективна и настолько переносима, насколько это возможно.


Ну, кроме функции ошибок - это свойственно мне. Пока вы правильно обрабатываете ошибки, вы должны быть в порядке."jlss.h" заголовок объявляетfcopy();"stderr.h" заголовок объявляетerr_syserr() среди многих других аналогичных функций сообщения об ошибках. Далее следует простая версия функции - настоящая добавляет имя программы и выполняет другие действия.

#include "stderr.h"
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void err_syserr(const char *fmt, ...)
{
    int errnum = errno;
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    if (errnum != 0)
        fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum));
    exit(1);
}

The code above may be treated as having a modern BSD license or GPL v3 at your choice.

2

размер каждого чтения должен быть кратным 512 (размер сектора) 4096 является хорошим

3

Что касается фактического ввода-вывода, то код, который я миллион раз писал в различных формах для копирования данных из одного потока в другой, выглядит примерно так. В случае успеха возвращается 0 или -1 с ошибкой, установленной при ошибке (в этом случае может быть скопировано любое количество байтов).

Обратите внимание, что для копирования обычных файлов вы можете пропустить EAGAIN, так как обычные файлы всегда блокируют ввод / вывод. Но неизбежно, если вы напишите этот код, кто-то будет использовать его в других типах файловых дескрипторов, поэтому считайте его халявой.

Есть специфичная для файла оптимизация, которая GNUcp делает, что меня здесь не беспокоило, что для длинных блоков по 0 байт вместо записи вы просто расширяете выходной файл, ища конец.

void block(int fd, int event) {
    pollfd topoll;
    topoll.fd = fd;
    topoll.events = event;
    poll(&topoll, 1, -1);
    // no need to check errors - if the stream is bust then the
    // next read/write will tell us
}

int copy_data_buffer(int fdin, int fdout, void *buf, size_t bufsize) {
    for(;;) {
       void *pos;
       // read data to buffer
       ssize_t bytestowrite = read(fdin, buf, bufsize);
       if (bytestowrite == 0) break; // end of input
       if (bytestowrite == -1) {
           if (errno == EINTR) continue; // signal handled
           if (errno == EAGAIN) {
               block(fdin, POLLIN);
               continue;
           }
           return -1; // error
       }

       // write data from buffer
       pos = buf;
       while (bytestowrite > 0) {
           ssize_t bytes_written = write(fdout, pos, bytestowrite);
           if (bytes_written == -1) {
               if (errno == EINTR) continue; // signal handled
               if (errno == EAGAIN) {
                   block(fdout, POLLOUT);
                   continue;
               }
               return -1; // error
           }
           bytestowrite -= bytes_written;
           pos += bytes_written;
       }
    }
    return 0; // success
}

// Default value. I think it will get close to maximum speed on most
// systems, short of using mmap etc. But porters / integrators
// might want to set it smaller, if the system is very memory
// constrained and they don't want this routine to starve
// concurrent ops of memory. And they might want to set it larger
// if I'm completely wrong and larger buffers improve performance.
// It's worth trying several MB at least once, although with huge
// allocations you have to watch for the linux 
// "crash on access instead of returning 0" behaviour for failed malloc.
#ifndef FILECOPY_BUFFER_SIZE
    #define FILECOPY_BUFFER_SIZE (64*1024)
#endif

int copy_data(int fdin, int fdout) {
    // optional exercise for reader: take the file size as a parameter,
    // and don't use a buffer any bigger than that. This prevents 
    // memory-hogging if FILECOPY_BUFFER_SIZE is very large and the file
    // is small.
    for (size_t bufsize = FILECOPY_BUFFER_SIZE; bufsize >= 256; bufsize /= 2) {
        void *buffer = malloc(bufsize);
        if (buffer != NULL) {
            int result = copy_data_buffer(fdin, fdout, buffer, bufsize);
            free(buffer);
            return result;
        }
    }
    // could use a stack buffer here instead of failing, if desired.
    // 128 bytes ought to fit on any stack worth having, but again
    // this could be made configurable.
    return -1; // errno is ENOMEM
}

Чтобы открыть входной файл:

int fdin = open(infile, O_RDONLY|O_BINARY, 0);
if (fdin == -1) return -1;

Открыть выходной файл довольно сложно. В качестве основы вы хотите:

int fdout = open(outfile, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0x1ff);
if (fdout == -1) {
    close(fdin);
    return -1;
}

Но есть смешивающие факторы:

  • you need to special-case when the files are the same, and I can't remember how to do that portably.
  • if the output filename is a directory, you might want to copy the file into the directory.
  • if the output file already exists (open with O_EXCL to determine this and check for EEXIST on error), you might want to do something different, as cp -i does.
  • you might want the permissions of the output file to reflect those of the input file.
  • you might want other platform-specific meta-data to be copied.
  • you may or may not wish to unlink the output file on error.

Очевидно, что ответы на все эти вопросы можно сделать так же, какcp& Quot ;. В этом случае ответ на первоначальный вопрос «игнорировать все, что я или кто-либо еще сказал, и использовать источникcp& Quot ;.

Между прочим, получение размера кластера файловой системы практически бесполезно. Вы почти всегда увидите увеличение скорости с увеличением размера буфера спустя много времени после того, как вы пропустили размер блока диска.

RelatedQuestions