在 Lobsters 看到「I Thought I Found a Bug...」這篇,裡面提到了 POSIX 以及 ISO C 標準的 fread()
與 fwrite()
其實有個討厭的限制。
在使用 fopen()
時可以指定使用 update mode (+
,像是 r+
、w+
以及 a+
) 開啟,然後可以針對這個 FILE *
讀寫:
fp = fopen("/tmp/foo.txt", "a+");
作者沒有注意到這邊其實有地雷 (我也是?),其中被指出來的是讀與寫操作不能直接交錯進行,這點從 1980 年的 AT&T UNIX System III manual 到 2024 年的 ISO C23 都是如此,你需要先呼叫 fflush()
或是 file positioning function 才能使用另外一個操作:(這邊引用的是 ISO C23 的版本)
However, output shall not be directly followed by input without an intervening call to the fflush function or to a file positioning function (fseek, fsetpos, or rewind), and input shall not be directly followed by output without an intervening call to a file positioning function, unless the input operation encounters end-of-file. Opening (or creating) a text file with update mode may instead open (or create) a binary stream in some implementations.
也就是讀後不可以直接寫;寫後不可以直接讀,除非已經在檔案尾巴。
不過 Linux 下的 glibc 與 FreeBSD 的 libc 都直接處理掉這個問題了:
Reads and writes may be intermixed on read/write streams in any order, and do not require an intermediate seek as in previous versions of stdio. This is not portable to other systems, however; ISO/IEC 9899:1990 (“ISO C90”) and IEEE Std 1003.1 (“POSIX.1”) both require that a file positioning function intervene between output and input, unless an input operation encounters end-of-file.
Linux 下的 musl 沒找到 manpage,不確定情況,不過 BSD 這邊可以翻到 OpenBSD 的 fopen.3 沒有處理這塊:
Reads and writes cannot be arbitrarily intermixed on read/write streams. ANSI C requires that a file positioning function intervene between output and input, unless an input operation encounters end-of-file.
NetBSD 的 fread.3 也有提到類似的情況,但文件上面的限制更嚴格,要求就是要用 fsetpos()
(那個 explicitly):
Mixing fread() and fwrite() calls without setting the file position explicitly using fsetpos(3) between read and write or write and read operations will lead to unexpected results because of buffering the file pointer not being set to the expected position after each operation completes. This behavior is allowed by ANSI C for efficiency and it will not be changed.
作者有提到透過 noop operation 直接 seek 到現在位置 (等於沒做事) 來避免問題:
fseek( f, 0, SEEK_CUR );
不確定在 NetBSD 下 fflush()
與 fseek()
是否照著 POSIX 標準進行,但看起來更保險的方式是用更麻煩的 fsetpos()
,如果考慮到 NetBSD 的話?
或是放掉 NetBSD?XD