理解C語言的I/O流

流和文件描述符

    當需要向文件輸入或輸出時,有兩種基本機制表示程序和文件之間的連接:文件描述符(file descriptor)和流(stream)。文件描述符表示爲int類型的對象,流表示爲 FILE * 類型的對象。
    文件描述符爲輸入和輸出操作提供了原始的低級接口。文件描述符和流都可以作爲終端與設備進行連接,也可以用於普通文件與進程之間的通信,通信方法包括管道和socket。針對某種特定類型的設備,如果要執行某個具體的控制操作,則必須使用文件描述符,這種情況下設備不提供流的工作方式。如果程序需要在某種特殊模式下輸入輸出,比如非阻塞(或輪詢)輸入,同樣需要使用文件描述符。
    相比於文件描述符,流提供了更高層次的接口。流接口對各種文件的處理非常相似,唯一的例外是三種緩衝(buffer)樣式有所不同。三種緩衝樣式分別爲:無緩衝流、行緩衝流和全緩衝流。無緩衝流的特點是字符寫入文件時獨立傳輸,字符之間沒有聯繫;當遇到換行符時,寫入行緩衝流的字符會以塊的形式傳輸到文件;全緩衝流的字符能夠組成任意大小的塊寫入文件。
    流接口的主要優點是,用於輸入輸出的函數集比文件描述符的相應功能更豐富,更強大,如格式化輸入輸出功能(printf 和 scanf函數)、面向字符和麪向行的輸入輸出功能。文件描述符接口僅提供用於傳輸字符塊的簡單功能,而且文件描述符的可移植性不如流,非GNU系統可能根本不支持文件描述符。

流和線程

    由於歷史原因,C數據結構中表示流的類型被稱爲FILE,而不是stream。FILE類型在頭文件stdio.h中被定義。
    流在多線程應用程序中的使用方式與在單線程應用程序中的使用方式相同。POSIX標準要求缺省情況下流操作是原子的,體現在:當兩個線程同時向一個流發出操作時,操作會依次執行,就像有先後順序一樣;當執行讀或寫操作時,緩衝區受保護,不執行同一流的其他操作。但函數本身將僅確保其自身操作的原子性,而不確保所有函數調用的原子性,如果程序需要原子調用多個流函數,需要在應用程序代碼中執行流鎖定。

文本流和二進制流

    打開一個流時,可以指定使用文本流(text stream)或二進制流(binary stream)。

FILE * fopen (const char *filename, const char *opentype)

    opentype中的字符“b”表示請求二進制流而不是文本流,但在POSIX系統(包括GNU系統)中沒有區別。fopen函數默認以文本流形式打開文件。
    文本流和二進制流在幾個方面有所不同:

  • 從文本流讀取的數據會被換行符 ‘\n’ 分成不同行,而二進制流則只是一長串字符。在某些系統上,文本流可能無法處理多於254個字符的行。
  • 在某些系統上,文本文件只能包含打印字符、水平製表符和換行符,因此文本流可能不支持其他字符。但是,二進制流可以處理任何字符。
  • 再次讀入文件時,在文本流中緊接換行符之前寫入的空格字符可能會消失。
  • 一般而言,從文本流讀取或寫入文本流的字符與實際文件中的字符之間不需要一對一的映射。

    二進制流總是比文本流更強大,但文本流依然有自己存在的價值。在一些操作系統上,文本流與二進制流具有不同的文件格式,當面向文本的程序需要讀寫一個普通文本文件時,文本流依然是唯一的選擇。
    通過一個簡單的實驗,研究Linux上文本IO流和二進制IO流的區別。
    文本流程序內容如下:

#include<stdio.h>
int main(void)
{
    int num=12345;
    FILE * fp;

    fp=fopen("file.txt","w");
    fprintf(fp,"%d",num);
    fclose(fp);
    return 0;
}

    二進制流程序內容如下:

#include<stdio.h>
int main(void)
{
    int write_var=12345,read_var;
    FILE * fp;

    fp=fopen("binary.txt","w+");
    fwrite(&write_var,sizeof(int),1,fp);

    rewind(fp);

    fread(&read_var,sizeof(int),1,fp);
    printf("%d\n",read_var);
    fclose(fp);

    return 0;
}

    分別運行兩個程序,文本IO流程序生成file.txt文件;二進制IO流程序生成binary.txt文件,同時在命令行顯示“12345”。兩個程序都是將整數12345寫入文件,但實際上是不一樣的。
    (1)生成文件的內容不同。

# cat file.txt 
12345
# cat binary.txt 
90

    cat命令查看文件時,以ASCII字符的格式顯示內容。因爲fprintf函數是文件IO函數,存儲整數12345的時候是分別把字符 ‘1’、‘2’、‘3’、‘4’、'5’的二進制編碼寫入文件中,所以讀取的時候還是12345。而fwrite將整數12345的二進制編碼0011 0000 0011 1001寫入文件,因爲本實驗的主機使用大端存儲的方式,所以二進制的低8位被放到內存的高地址上,高位放到了內存低地址上,以字符格式讀取的時候,0011 1001被解析成ASCII的字符9,0011 0000被解析成ASCII的字符0,最後顯示90。
    (2)生成文件的大小不同。

# ll file.txt binary.txt 
-rw-r--r--. 1 root root 4 May 29 15:39 binary.txt
-rw-r--r--. 1 root root 5 May 29 09:48 file.txt

    文件IO生成的file.txt存儲的內容爲5個字符,因爲在本實驗中,每個char字符佔用1個字節,每個int型整數佔用4個字節,所以file.txt大小爲5字節。
    二進制編碼0011 0000 0011 1001需要佔用2字節的存儲空間,但在fwrite函數中,明確定義了使用sizeof(int)大小的空間來存放變量write_var,所以binary.txt的大小爲4字節,binary.txt後面的兩個字節使用字符^@進行填充,該字符在ASCII表中的值爲0,表示NULL。

# cat -A binary.txt
90^@^@

    (3)生成文件的格式不同。將二進制程序fopen函數的 w+ 參數改爲 a+,以便追加內容,重新編譯並執行兩次,此時,binary.txt的內容爲:

# cat -A binary.txt 
90^@^@90^@^@

    使用file命令查看格式,file命令一般將文件分爲3種類型:文本文件、可執行文件和數據。binary.txt的文件類型爲data,可以很清楚的看到二進制流與文本流的區別。

# file binary.txt file.txt 
binary.txt: data
file.txt:   ASCII text, with no line terminators

參考文獻

[1]Ian Darwin等.file(1) - Linux man page[EB/OL].https://linux.die.net/man/1/file,1973-11-01.
[2]GNU.The GNU C Library[EB/OL].https://www.gnu.org/software/libc/manual/html_node/index.html,2020-01-01.
[3]Stephen Prata.C Primer Plus(第五版)中文版[M].人民郵電出版社:北京,2014:354.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章