首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言低階 I/O 實戰:健壯的文件拷貝程式實作

     編輯:關於C語言

前言

在系統程式設計中,掌握低階文件 I/O(open、read、write、close)是必備技能。本文解析一個經典的 C 語言文件拷貝程式,展示如何正確處理部分讀寫、中斷信號(EINTR)等邊界情況,確保數據完整性。

核心程式碼解析

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>

#define BUFFER_SIZE 1024

int main(int argc, char **argv) {
    int from_fd, to_fd;
    int bytes_read, bytes_write;
    char buffer[BUFFER_SIZE];
    char *ptr;

    // 1. 參數檢查
    if (argc != 3) {
        fprintf(stderr, "Usage: %s fromfile tofile\n\a", argv[0]);
        exit(1);
    }

    // 2. 開啟源檔案 (唯讀)
    if ((from_fd = open(argv[1], O_RDONLY)) == -1) {
        fprintf(stderr, "Open %s Error: %s\n", argv[1], strerror(errno));
        exit(1);
    }

    // 3. 建立/開啟目標檔案 (寫入、創建、用戶讀寫權限)
    if ((to_fd = open(argv[2], O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) == -1) {
        fprintf(stderr, "Open %s Error: %s\n", argv[2], strerror(errno));
        exit(1);
    }

    // 4. 核心拷貝循環
    while (bytes_read = read(from_fd, buffer, BUFFER_SIZE)) {
        // 處理讀取錯誤:忽略 EINTR(被信號中斷),其他視為致命錯誤
        if ((bytes_read == -1) && (errno != EINTR)) break;
        else if (bytes_read > 0) {
            ptr = buffer;
            // 內層循環:確保所有讀取的位元組都被寫入
            while (bytes_write = write(to_fd, ptr, bytes_read)) {
                if ((bytes_write == -1) && (errno != EINTR)) break;
                else if (bytes_write == bytes_read) break; // 寫入完成
                else if (bytes_write > 0) { // 部分寫入
                    ptr += bytes_write;
                    bytes_read -= bytes_write;
                }
            }
            if (bytes_write == -1) break; // 寫入致命錯誤
        }
    }

    // 5. 清理資源
    close(from_fd);
    close(to_fd);
    exit(0);
}

關鍵技術點深度剖析

1. 健壯的讀寫循環設計

外層 while (bytes_read = read(...)) 處理讀取。read 可能返回:

  • > 0:實際讀取的位元組數(可能小於 BUFFER_SIZE,尤其在檔案尾部)。
  • 0:EOF,循環結束。
  • -1:錯誤。若 errno == EINTR(被信號中斷),應重試;否則中止。

2. 部分寫入的處理

write 不保證一次寫入所有請求的位元組(如管道、網路套接字、磁碟滿等情況)。內層循環透過 ptr 和遞減的 bytes_read 追蹤未寫入數據,直到全部寫完或發生致命錯誤。這是「經典拷貝代碼」的精髓所在。

3. 錯誤報告與 errno

使用 strerror(errno) 將錯誤碼轉為人類可讀訊息,並輸出至 stderr,符合 Unix 哲學:靜默成功,喧嘩失敗。

4. 檔案權限 S_IRUSR | S_IWUSR

創建新檔時明確指定擁有者讀寫權限(0600),避免預設權限過寬(如 0666 受 umask 影響)造成安全隱患。

進階補充:現代替代方案

對於單純的檔案拷貝,現代系統提供更高效的系統呼叫:

  • sendfile(2):內核態直接在檔案描述符間傳輸數據,避免用戶態緩衝區拷貝(零拷貝)。
  • copy_file_range(2) (Linux 4.5+):同樣在內核態完成,支援檔案系統級優化(如 reflink)。

但理解本文的 read/write 循環對於掌握系統程式基礎、處理非一般檔案(如設備、Socket)至關重要。

總結

這段代碼雖短,卻涵蓋了系統程式設計的核心精髓:資源獲取與釋放、錯誤處理、部分 I/O 處理、信號安全。它是每個 C 程式設計師都應熟練掌握的「手藝活」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved