Eug*_*jak 9 c file-io copy stdio
这看起来像一个简单的问题,但我在这里找不到类似的东西.
由于C中没有文件复制功能,我们必须自己实现文件复制,但我不喜欢重新发明轮子,即使是那样的琐碎事情,所以我想问云:
此代码应该是可移植(在Windows/MAC/LINUX/BSD/QNX/younameit),稳定的,经过时间考验的,速度快,内存使用效率等进入特定系统的内部挤出一些性能是值得欢迎的(就像得到文件系统的簇大小) .
这似乎是一个微不足道的问题,但是,例如,CP命令的源代码不是10行C代码.
这是我需要从一个文件复制到另一个文件时使用的功能 - 使用测试工具:
/*
@(#)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 */
Run Code Online (Sandbox Code Playgroud)
很明显,这个版本使用来自标准I/O的文件指针,而不是文件描述符,但它相当有效并且尽可能便携.
好吧,除了错误功能 - 这对我来说是特殊的.只要你干净地处理错误,你应该没问题.该"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);
}
Run Code Online (Sandbox Code Playgroud)
上述代码可视为具有现代BSD许可证或GPL v3.
就实际的 I/O 而言,我以各种形式编写了一百万次用于将数据从一个流复制到另一个流的代码是这样的。成功时返回 0,出错时返回 -1,并设置 errno(在这种情况下,可能会复制任意数量的字节)。
请注意,对于复制常规文件,您可以跳过 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
}
Run Code Online (Sandbox Code Playgroud)
要打开输入文件:
int fdin = open(infile, O_RDONLY|O_BINARY, 0);
if (fdin == -1) return -1;
Run Code Online (Sandbox Code Playgroud)
打开输出文件很棘手。作为基础,您需要:
int fdout = open(outfile, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0x1ff);
if (fdout == -1) {
close(fdin);
return -1;
}
Run Code Online (Sandbox Code Playgroud)
但存在一些令人困惑的因素:
cp -i做一样。显然,所有这些问题的答案都可以是“做同样的事情cp”。在这种情况下,原始问题的答案是“忽略我或其他任何人所说的一切,并使用来源cp”。
顺便说一句,获取文件系统的簇大小几乎没有用。在超过磁盘块的大小之后很长一段时间,您几乎总是会看到速度随着缓冲区大小的增加而增加。