2009-06-17 25 views
9

Điều này trông giống như một câu hỏi đơn giản, nhưng tôi không tìm thấy bất cứ điều gì tương tự ở đây.Đã cố gắng và đúng mã sao chép tệp đơn giản trong C?

Vì không có chức năng sao chép tệp trong C, chúng tôi phải thực hiện sao chép tệp, nhưng tôi không muốn phát minh lại bánh xe ngay cả đối với những thứ tầm thường như vậy, vì vậy tôi muốn hỏi đám mây:

  1. Bạn sẽ đề xuất mã nào để sao chép tệp bằng fopen()/fread()/fwrite()?
    • Bạn sẽ đề xuất mã nào để sao chép tệp bằng cách sử dụng open()/read()/write()?

Mã này nên cầm tay (Windows/Mac/Linux/BSD/QNX/younameit), ổn định, thời gian thử nghiệm, nhanh chóng, bộ nhớ hiệu quả và vv Bắt vào internals hệ thống cụ thể để ép một số hiệu suất hơn được hoan nghênh (giống như nhận được kích thước của hệ thống tập tin).

Điều này có vẻ như một câu hỏi tầm thường nhưng, ví dụ, mã nguồn cho lệnh CP không phải là 10 dòng mã C.

Trả lời

3

Theo như I/O thực tế, mã tôi đã viết hàng triệu lần trong các bí quyết khác nhau để sao chép dữ liệu từ luồng này sang luồng khác sẽ diễn ra như thế này. Nó trả về 0 thành công, hoặc -1 với errno được đặt trên lỗi (trong trường hợp này, bất kỳ số lượng byte nào có thể đã được sao chép).

Lưu ý rằng để sao chép các tệp thông thường, bạn có thể bỏ qua nội dung EAGAIN vì các tệp thông thường luôn chặn I/O. Nhưng chắc chắn nếu bạn viết mã này, ai đó sẽ sử dụng nó trên các loại mô tả tập tin khác, vì vậy hãy xem xét nó một freebie.

Có tối ưu hóa tệp cụ thể mà GNU cp thực hiện, mà tôi không bận tâm ở đây, đối với các khối dài 0 byte thay vì viết, bạn chỉ cần mở rộng tệp đầu ra bằng cách tìm kiếm kết thúc.

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 
} 

Để mở tập tin đầu vào:

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

Mở tập tin đầu ra là tinh quái. Là một cơ sở, bạn muốn:

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

Nhưng cũng có những yếu tố gây nhiễu:

  • bạn cần phải đặc biệt hợp cụ thể khi các tập tin là như nhau, và tôi không thể nhớ làm thế nào để làm điều đó portably .
  • nếu tên tệp đầu ra là một thư mục, bạn có thể muốn sao chép tệp vào thư mục.
  • nếu tệp đầu ra đã tồn tại (mở với O_EXCL để xác định điều này và kiểm tra EEXIST có lỗi), bạn có thể muốn làm điều gì đó khác, như cp -i.
  • bạn có thể muốn các quyền của tệp đầu ra phản ánh các quyền của tệp đầu vào.
  • bạn có thể muốn siêu dữ liệu nền tảng cụ thể khác được sao chép.
  • bạn có thể hoặc không muốn hủy liên kết tệp đầu ra do lỗi.

Rõ ràng là câu trả lời cho tất cả các câu hỏi này có thể là "làm tương tự như cp". Trong trường hợp đó, câu trả lời cho câu hỏi ban đầu là "bỏ qua mọi thứ tôi hoặc bất kỳ ai khác đã nói và sử dụng nguồn của cp".

Btw, nhận kích thước cụm của hệ thống tệp bên cạnh vô dụng. Bạn sẽ hầu như luôn thấy tốc độ tăng lên với kích thước bộ đệm dài sau khi bạn đã vượt qua kích thước của một khối đĩa.

+0

Mẫu của bạn không thể bù đắp buf theo số tiền đã ghi, điều này sẽ gây ra ghi không đầy đủ để khởi động lại từ đầu – Hasturkun

+0

Cảm ơn bạn. Luôn có một lỗi. –

+0

OP yêu cầu giải pháp di động, nhưng tôi thấy nó không hoạt động trên Windows. Để bắt đầu 'poll()' bị thiếu, và 'ssize_t' là một phần mở rộng POSIX. Không thể vượt qua, nhưng mã chắc chắn không hoạt động như vậy. –

1

Đây là ví dụ rất dễ hiểu và rõ ràng: Copy a file. Kể từ khi nó được viết bằng ANSI-C mà không có bất kỳ cuộc gọi chức năng cụ thể, tôi nghĩ rằng điều này sẽ được khá nhiều di động.

+2

Đáng buồn thay, nó sử dụng fgetc đó là khá hiệu quả. –

+0

Điểm tốt! Mặc dù nó rất rõ ràng và di động nó chắc chắn thiếu hiệu suất. – merkuro

+1

@David: Có phải fgetc() không hiệu quả? Stdio sẽ tự đệm bằng bộ đệm có kích thước BUFSIZ (8192 byte trên hệ thống của tôi).Nếu bạn đang sử dụng MSVC++, #define _CRT_DISABLE_PERFCRIT_LOCKS trong các chương trình đơn luồng. –

1

Tùy thuộc vào ý bạn bằng cách sao chép tệp, điều đó chắc chắn còn xa tầm thường. Nếu bạn có nghĩa là sao chép nội dung chỉ, sau đó có gần như không có gì để làm. Nhưng nói chung, bạn cần sao chép siêu dữ liệu của tệp và điều đó chắc chắn phụ thuộc vào nền tảng. Tôi không biết của bất kỳ thư viện C mà làm những gì bạn muốn một cách cầm tay. Chỉ cần xử lý tên tập tin của chính nó là không có vấn đề tầm thường nếu bạn quan tâm đến tính di động.

Trong C++, có thư viện tập tin trong boost

1

Một điều tôi nhận thấy khi thực hiện sao chép tập tin của riêng tôi, và nó có vẻ hiển nhiên, nhưng nó không phải là:/O của tôi là chậm. Bạn có thể khá nhiều thời gian tốc độ sao chép của bạn bằng cách bao nhiêu người trong số họ bạn làm. Vì vậy, rõ ràng bạn cần làm càng ít càng tốt.

Kết quả tốt nhất tôi tìm thấy là khi tôi có một bộ đệm ginourmous, đọc toàn bộ tệp nguồn vào trong một I/O, sau đó viết toàn bộ bộ đệm ra khỏi nó trong một I/O. Nếu tôi thậm chí phải làm điều đó trong 10 đợt, nó có cách chậm. Cố gắng đọc và viết ra từng byte, giống như một coder naieve có thể thử trước, chỉ là đau đớn.

5

Đây là chức năng tôi sử dụng khi tôi cần phải sao chép từ một file khác - với khai thác thử nghiệm:

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

Rõ ràng, phiên bản này sử dụng con trỏ tập tin từ tiêu chuẩn I/O và không nộp mô tả, nhưng nó là hợp lý hiệu quả và về di động như nó có thể được.


Vâng, ngoại trừ chức năng lỗi - điều đó đặc biệt đối với tôi. Miễn là bạn xử lý các lỗi một cách sạch sẽ, bạn sẽ ổn. Tiêu đề "jlss.h" tuyên bố fcopy(); tiêu đề "stderr.h" tuyên bố err_syserr() trong số nhiều hàm báo cáo lỗi tương tự khác. Một phiên bản đơn giản của hàm sau đây - một phiên bản thực sự bổ sung tên chương trình và thực hiện một số công cụ khác.

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

Đoạn mã trên có thể được coi là có một giấy phép BSD hiện đại hay GPL v3 ở sự lựa chọn của bạn.

+0

Tôi thích nó, đơn giản, sạch sẽ, hoạt động. Tôi đã sử dụng 4096 như BUFSIZ của tôi nhưng tôi giả định rằng bất kỳ bội số nào của 512 sẽ hoạt động tốt. –

+0

@jonathan Tôi mã này kích thước là gì nếu BUFSIZ. ? Tệp nguồn của tôi có thể xấp xỉ 50 MB.? vì vậy kích thước nào tốt cho tôi? – user1089679

+0

BUFSIZ được định nghĩa trong ' 'và là kích thước thích hợp cho bộ đệm tệp trên nền tảng. Nếu bạn muốn chịu trách nhiệm về kích thước bộ đệm, hãy sử dụng một tên khác và chỉ định kích thước của nó: 'enum {BUFFER_SIZE = 4096};' hoặc bất kỳ thứ gì bạn muốn sử dụng. Trong giới hạn rộng, kích thước bộ đệm lớn hơn nhanh hơn, nhưng sự tăng lên từ 4 KiB để nói 256 KiB không phải là tất cả những gì lớn, và bạn phải trao đổi không gian được sử dụng cho bộ đệm. Tùy thuộc vào nền tảng của bạn (ví dụ: máy chủ và thiết bị di động), bạn có thể điều chỉnh các lựa chọn của mình. 4 KiB đến 64 KiB sẽ đủ cho hầu hết các mục đích. –

2

kích thước của mỗi đọc cần phải là một bội số của 512 (kích thước sector) 4096 là một tốt nhất

Các vấn đề liên quan