在C中尝试和真正的简单文件复制代码?

这看起来像一个简单的问题,但我在这里找不到类似的东西。

由于C中没有文件复制function,我们必须自己实现文件复制,但我不喜欢重新发明轮子,即使是那样的琐碎事情,所以我想问云:

  1. 您建议使用fopen()/ fread()/ fwrite()进行文件复制的代码是什么?
    • 您建议使用open()/ read()/ write()进行文件复制的代码是什么?

这段代码应该是可移植的(windows / mac / linux / bsd / qnx / younameit),稳定,经过时间测试,快速,内存效率高等等。欢迎进入特定系统的内部以挤出更多性能(比如获取文件系统簇大小) 。

这似乎是一个微不足道的问题,但是,例如,CP命令的源代码不是10行C代码。

就实际的I / O而言,我用各种伪装编写了一百万次的代码,用于将数据从一个流复制到另一个流,就像这样。 它在成功时返回0,或者在错误时设置errno时返回-1(在这种情况下可能已经复制了任意数量的字节)。

请注意,对于复制常规文件,您可以跳过EAGAIN内容,因为常规文件始终阻止I / O. 但是如果你编写这段代码不可避免,有人会在其他类型的文件描述符上使用它,所以认为它是免费赠品。

GNU cp做了一个特定于文件的优化,我在这里没有烦恼,对于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; } 

但是有一些令人困惑的因素:

  • 当文件相同时你需要特殊情况,我不记得如何移植。
  • 如果输出文件名是目录,则可能需要将文件复制到目录中。
  • 如果输出文件已经存在(用O_EXCL打开来确定这个并且在出错时检查EEXIST),你可能想要做一些不同的事情,就像cp -i那样。
  • 您可能希望输出文件的权限反映输入文件的权限。
  • 您可能希望复制其他特定于平台的元数据。
  • 您可能希望也可能不希望在出错时取消链接输出文件。

显然,所有这些问题的答案可能是“与cp ”。 在这种情况下,原始问题的答案是“忽略我或其他任何人所说的一切,并使用cp的来源”。

顺便说一下,获取文件系统的簇大小几乎没用。 在您通过磁盘块的大小后,您几乎总是会看到缓冲区大小增加的速度。

这是我需要从一个文件复制到另一个文件时使用的function – 使用测试工具:

 /* @(#)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 */ 

显然,这个版本使用来自标准I / O的文件指针,而不是文件描述符,但它相当有效并且尽可能便携。


好吧,除了错误function – 这对我来说是特殊的。 只要你干净地处理错误,你应该没问题。 "jlss.h"标头声明了fcopy() ; "stderr.h"标头在许多其他类似的错误报告函数中声明了err_syserr() 。 该函数的简单版本如下 – 真正的函数添加程序名称并执行其他一些操作。

 #include "stderr.h" #include  #include  #include  #include  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); } 

上述代码可视为您拥有现代BSD许可证或GPL v3。

每次读取的大小需要是512的倍数(扇区大小)4096是一个很好的

这是一个非常简单明了的例子: 复制文件 。 由于它是用ANSI-C编写的,没有任何特定的函数调用,我认为这个可以非常便携。

根据您复制文件的含义,它肯定远非琐碎。 如果您的意思是仅复制内容,那么几乎无所事事。 但通常,您需要复制文件的元数据,这肯定取决于平台。 我不知道任何C库以便携方式做你想做的事。 如果您关心可移植性,仅仅处理文件名本身并非易事。

在C ++中, boost中有文件库

我在实现自己的文件副本时发现了一件事,它看起来很明显,但事实并非如此:I / O很 。 您可以根据自己的复制速度计算复制速度。 很明显,你需要做尽可能少的事情。

我发现最好的结果是当我得到一个ginourmous缓冲区,在一个I / O中读取整个源文件,然后在一个I / O中将整个缓冲区写回来。 如果我甚至不得不分10批做,那就慢了。 尝试读取和写出每个字节,就像一个naieve编码器可能首先尝试,只是痛苦。