檔案與輸入輸出

短讀與部分寫入

你向廚房點了十個三明治,他們卻給你四個,說「我現在就這些,再來問一次吧」。read() 與 write() 正是如此:你要求某個位元組數,但系統被允許只滿足你請求的一部分,並告訴你它實際做了多少。假設你每次都拿到全額,是 C 的 I/O 中最常見的臭蟲之一。

具體來說,read(fd, buf, n) 回傳它實際放進 buf 的位元組數,這個數可以是從 1 到 n 之間的任何值——這叫短讀——或在檔案結尾時為 0,或出錯時為 -1。短讀是正常的,不是失敗:從管線、終端機或通訊端讀取,往往回傳當下可得的量,而非等到湊滿全部 n 個位元組。同樣地,write(fd, buf, n) 回傳它實際接受的位元組數,這可能少於 n——這叫部分寫入——尤其在管線與通訊端上,或當磁碟寫滿時。正確的模式是一個迴圈:持續呼叫,每次都把你的緩衝區指標往前推、把你的計數依搬移量縮減,直到全部傳完、或遇到 EOF、或遇到真正的錯誤為止。把回傳 -1 且 errno == EINTR(被信號中斷)當成「再試一次就好」,而非失敗。

為何重要:寫成 ssize_t n = read(fd, buf, 100); 然後盲目把 buf 當成正好裝著 100 個位元組的程式碼是壞的——它裡面可能只有 12 個。這個臭蟲常常藏得很好,因為從快速磁碟上的普通檔案讀取,經常確實會回傳全額,所以程式碼在測試時「能動」,然後在它從管線或慢速網路讀取的那天毀掉資料。永遠要檢查回傳值並用迴圈;你要求的位元組數,和你拿到的位元組數,是兩個不同的數字。

/* 寫出全部 n 個位元組,用迴圈處理部分寫入 */ size_t left = n; const char *p = buf; while (left > 0) { ssize_t w = write(fd, p, left); if (w < 0) { if (errno == EINTR) continue; return -1; } p += w; left -= w; }

絕不相信一次 write() 就能搬完全部;推進指標並用迴圈,直到 left == 0。

短讀不是錯誤,而 read() == 0 表示 EOF,不是失敗。要求的位元組數與搬移的位元組數是各自獨立的數字——忽略 read/write 回傳值的程式碼,是最常見、又最悄悄具破壞性的 C 臭蟲之一。

又称
partial read/writeincomplete I/O不完整的讀寫