Copyright (慣C) 2018, 2019 宅色夫
data flow for the standard input (0), output (1), and error (2) streams
[ source ]
在 UNIX 的設計哲學是 "Everything is a file",扣除少數的例外 (如 BSD socket),任何資源都可當作檔案來操作,不過當我們查閱 C 語言手冊和文獻時,"file" 的操作被分類在 stream I/O 中,聽起來有點奇怪吧?而 stream I/O 是 C 語言心裡最軟的一塊,這話怎説?
我們先來看 <
ctype.h>
標頭檔宣告的 islower() 函式,其作用很單純,就是判定輸入的「字元」是否為英文小寫字元,不過在宣告中,islower() 函式的參數卻是 int 型態,類似的現象也出現於 getchar() 函式的回傳值,也是 int 型態。在現行 64 位元處理器架構上運作的作業系統常用 32-bit 表示 int 型態,甚至某些作業系統用 64-bit 表示 int 型態,讓我們不禁納悶:「C 語言不是很講究效率嗎?能用 1 個 byte (char) 處理的資料,為何要用 4 個 bytes 甚至更多 (int) 去表達呢?」
簡單來說,要考慮到 EOF (end-of-file)!後者跟編譯器與執行環境有關,上述的 islower() 或 getchar() 之所以將原本 char 型態可保存的資料「擴充」為 int 型態,即是考慮到輸入和輸出的過程可能被某種情況或條件給「打斷」,倘若要深入理解更多, 就得回到 stream I/O 的原理。此外,從 C89, C99 規格就存在的 signal,依據手冊的說法,這是 "ANSI C signal handling",實務上會在 Stream I/O 使用中作為例外處理機制,本議程預計分析現有開放原始碼專案中的情境,探討 Stream I/O, EOF 和 signal 之間的微妙關聯。
延伸閱讀: Redirection in bash
C++ 標準函式庫竟然有 ios::good
「iOS 真棒!」
C99 規格書 7.19.6.2 談及 fscanf
一類函式的行為, 第 5 段:
"A directive composed of white-space character(s) is executed by reading input up to the first non-white-space character (which remains unread), or until no more characters can be read."
end-of-file 的設定是由輸入函式在讀取資料的過程中設定的。
搜尋 glibc 原始程式碼,可發現 EOF
定義在 libio/stdio.h
,但這是 Define ISO C stdio on top of C++ iostreams
ctype.h
實作於 glibc 原始程式碼的 ctype/ctype.h
這裡要補充一點, 在有些系統 (特別是 UNIX 系統) 上, 每一行資料 (這裡只指 text 資料) 必須以 end-of-line 來分割, 包括最後一行. 否則, 某些程式語言在, 或系統處理最後一行時有可能會出問題。
最好是這樣:
以 ctrl-Z 做文字檔的 end-of-file marker 源自 DEC 早期的系統, 後來被 CP/M 借用, 再後來用在 MS-DOS 上。
使用 ctrl-Z 的原因是因為在早期的檔案系統,檔案長度是以 128-byte 的 sector 為單位的, 當檔案的大小不是 sector 的整數倍數的話, ctrl-Z 用來標示檔案的真正結尾, 並以 ctrl-Z 把剩餘的 sector 填滿。
自 MS-DOS 2.0 起就可以正確的記綠檔案的正確長度, 已可不需要用 ctrl-Z 的機制了. 但這個機制到目前還在支援. 因為在某個情況下它非常有用。
當你在 console 上用鍵盤來輸入資料時, 可以用 ctrl-Z 來產生 end-of-file 訊息。
UNIX 系統上用 ctrl-D 來 signal end-of-file.
以目前的系統來說, EOF 只是一個概念上的存在, 文字檔並不需要這個 marker,文字編輯器不會自動的加入這個 marker。
在 MS-Windows 上, ctrl-Z 不再做為檔案大小的依據, 也不會導致 file truncation. Ctrl-Z 可以存在文字檔內, 但它會影響輸入函式的行為: 如果檔案是以 text mode 來開啟的話, ctrl-Z 會終結輸入,即使 ctrl-Z 後面還有資料也是一樣。
Standard I/O
C++ iostream 的 cin
, cout
和 C stdio 的 scanf
, printf
效能比較
或者透過 typedef 得到的等效且簡短的宣告:
C Notes for Professionals
Chapter 31 (對應 Page 208)
signal numbers can be synchronous (like SIGSEGV– segmentation fault) when they are triggered by a malfunctioning of the program itself or asynchronous (like SIGINT - interactive attention) when they are initiated from outside the program, e.g by a keypress as Cntrl-C.
合法的 C11 程式:
注意 quick_exit 在 C11 才出現,在 C99 可改寫為:
POSIX recommends the usage of sigaction() instead of signal(), due to its underspecified behavior and significant implementation variations. POSIX also defines many more signals than ISO C standard, including SIGUSR1 and SIGUSR2, which can be used freely by the programmer for any purpose.
signal 的存在並非總是「善後」的行為,而是藉由其同步和非同步的處理,讓程式設計者能先專注在 business logic,再來思考例外狀況該如何回應。以 mazu-editor (媽祖程式碼編輯器) 為例:
當使用者嘗試調整終端機模擬程式的有效寬度和高度,從原本:
到後來:
就涉及到刷新畫面的操作,但若程式都要週期性更新,這樣不足以反映操作,於是可藉由 SIGWINCH
(Window size change) signal 及其註冊的 handler 來實作更新。
errno
perror
, strerror
EXIT_SUCCESS
, EXIT_FAILURE
觸發 SIGFPE signal
根據 IEEE 754 7.2 節的說明,會產生 quiet NAN 的運算如下:
會產生 signal NAN 的運算如下:
Page fault
"Time is an illusion. Lunchtime doubly so."
― Douglas Adams, The Hitchhiker's Guide to the Galaxy
fork
,則可用這個命令計算 '-' 數量: ./fork | wc -c
sudo sysctl -w kernel.sched_child_runs_first=1
watch -n 1 ./test
每秒更新執行結果一次。耐心凝視結果一陣子,是否發現輸出結果會跳動?sudo sysctl -w kernel.sched_child_runs_first=1
,以便要求 Linux 排程器 (CFS) 讓 child process 優先於 parent process 執行posix_spawn
以避免該問題。fork(2) calls slow down as the parent process uses more memory due to the need to copy page tables. In many common uses of fork(), where it is followed by one of the exec family of functions to spawn child processes (Kernel#system, IO::popen, Process::spawn, etc.), it's possible to remove this overhead by using special process spawning interfaces (posix_spawn(), vfork(), etc.)