C標準輸出入函數庫 與 作業系統
開啟檔案、讀寫檔案
fopen() #include <stdio.h> int main(int argc, char **argv) { FILE* file; file = fopen("./tmp", "w"); fprintf(file, "this is a tmp file\n"); return 0; }
fopen() #include <stdio.h> FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd, const char *mode); 回傳值是FILE這個資料型別 初始化FILE只能用stdio定義的函數操作,如:fopen、fdopen fdopen可以將上一個章節教的file descriptor轉換為FILE物件
fopen的mode參數 fopen可以接多種參數,常用的參數如下,修飾參數接在參數之後 參數 代表意義 r 開啟檔案以供讀取(檔案要原本就存在) w 開啟檔案以供寫入。如果原本就有這個檔案,原先檔案的內容會被清除。原本沒有這個檔案,系統自動建立此檔案。 a 開啟檔案以供寫入,所寫入的資料附加於原檔案之後。原本沒有這個檔案,系統自動建立此檔案。 r+ 和「r」一樣,但打開的檔案可供「讀、寫」 w+ 和「w」一樣,但打開的檔案可供「讀、寫」 a+ 和「a」一樣,但打開的檔案可供「讀、寫」 某些作業系統還提供b這個mode(例如:rb),代表binary,但UNIX並不特別區分「字」與「二進位碼」,因此我們可以忽略b這個mode(如果加上「b」UNIX,也會忽略這個mode)。
stdin、stdout、stderr 在UNIX內,一啟動程式作業系統就會自動幫我們開啟三個「檔 案」,分別是標準輸入、標準輸出及標準錯誤輸出,這三個檔案 對應的FILE物件如下 FILE物件 「通常」的設備 標準輸入 stdin 鍵盤 標準輸出 stdout 螢幕 標準錯誤輸出 stderr
fprintf printf將「格式化」後的字串印到標準輸出(通常是螢幕) fprintf將「格式化」後的字串印到檔案 #include <stdio.h> int printf(const char * restrict format, ...) int fprintf(FILE * restrict stream, const char * restrict format, ...) int sprintf(char* restrict str, const char * restrict format, ...) printf將「格式化」後的字串印到標準輸出(通常是螢幕) fprintf將「格式化」後的字串印到檔案 sprintf將「格式化」後的字串印到「記憶體」
Example: fprintf & mode #include <stdio.h> int main(int argc, char **argv) { FILE* file; file = fopen("./tmp", argv[1]); fprintf(file, "this_is_a_tmp_file\n");; fclose(file); return 0; }
執行結果 $./write+read2 a+ $less ./tmp this_is_a_tmp_file (END) 使用完檔案後,用fclose關閉檔 案
檔案位置(position) int fseek(FILE *stream, long offset, int whence); long ftell(FILE *stream); 這二個函數分別會設定(fseek)和回傳檔案位置(position),特別要注意 的是fseek並不會回傳目前的檔案位置,因此要得到檔案位置必須使用ftell 和lseek很像,fseek提供三個選項,分別是SEEK_SET, SEEK_CUR, SEEK_END 成功回傳0,失敗回傳-1
append + fseek #include <stdio.h> int main() { FILE *stream; char buf[100]; int ret; stream = fopen("./tmp", "a+"); ret = fseek(stream, 2, SEEK_SET); fread(buf, 100, 1, stream); printf(buf); printf("\nreturn value = %d\n", ret); fprintf(stream, "append?"); printf("position = %d\n", ftell(stream)); return 0; }
結果 $./append+fseek this_is_a_tmp_file append? return value = 0 position = 85 $less ./tmp 使用a, a+打開的stream也可以使 用fseek,回傳值為0 fseek可以改變「讀取位置」, fseek會改變讀取的資料的位置 但實際上「寫入的資料會放在檔 案的最後面」
C函數庫的buffer #include <stdio.h> int fflush(FILE *stream); 系統內部有二個重要的buffer,分別位於C函數庫(如果我們使 用的是stdio相關的函數),另一個位於核心(Linux kernel), 當執行fflush時會將C函數庫內所有被buffer的資料寫到OS。 如果想要確保OS的buffer資料也寫入到硬碟則需要使用fsync,但 我們只有FILE物件沒有file descriptor。此時可以使用int fileno(FILE *stream) 。
設定buffer的大小 #include <stdio.h> void setbuf(FILE *stream, char *buf); void setbuffer(FILE *stream, char *buf, size_t size); void setlinebuf(FILE *stream); int setvbuf(FILE *stream, char *buf, int mode, size_t size);
以setvbuf為例 int setvbuf(FILE *stream, char *buf, int mode, size_t size); 依照mode的指示設定stream,並且以buf為buffer,該buffer的大小為size setvbuf必須在「真正使用」stream前使用才會有效果 請記得,使用setvbuf前應該要有這樣的動作:buf=malloc(size); mode有三種選項 _IONBF unbuffered,所有寫入到stream的物件立即寫入到stream _IOLBF line buffered,當遇到換行符號(\n)才將該「字串」寫到stream _IOFBF fully buffered,當buffer滿了,才將這些物件寫入stream
setvbuf #include <stdio.h> sscanf(argv[1], "%d", &bufSize); #include <stdlib.h> buf = (char*)malloc(bufSize); setvbuf(stream, buf, _IOFBF, bufSize); int main(int argc, char **argv) for (i=0; i<dataSize; i++) { fwrite("d", 1, 1, stream); FILE* stream; return 0; int bufSize; } int dataSize = 10000000; char *buf; int i; stream = fopen("./tmp", "w+");
結果 bufsize real user sys 100 10.666s 0.104s 6.248s 1000 0.725s 0.308s 10000 0.239s 0.016s
讀寫檔案 以fread為例,從stream讀取nmemb筆資料,每筆資料的大小為size這麼大, 到ptr所指向的buffer #include <stdio.h> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 以fread為例,從stream讀取nmemb筆資料,每筆資料的大小為size這麼大, 到ptr所指向的buffer 以fwrite為例, 從ptr所指向的buffer的資料,寫出到stream。共寫出nmemb 筆資料,每筆資料的大小為size這麼大 fread及fwrite的回傳值都是「讀取幾筆資料」,不是讀取多少個字元
Errors 及 EOF int feof(FILE *stream); int ferror(FILE *stream); void clearerr(FILE *stream); 以fread為例,不管是發生錯誤或者讀到檔案結尾,回傳值都小於 預期(例如小於要寫出的資料量)。那我們要怎樣區分EOF和 Error? feof、ferror可以分別測出到底是檔案結尾或者是錯誤 測試完畢以後呼叫clearerr清除該「錯誤標示」
feof #include <stdio.h> if (ferror(stream)) printf("error\n"); int main() { if (feof(stream)) FILE *stream; printf("EOF\n"); char buf[5000]; } int ret; return 0; stream = fopen("./tmp", "a+"); ret=fread(buf, 10, 500, stream); printf("ret = %d\n", ret); if(ret!=1) {
寬字串(wide string, i18n)
strlen.c #include <stdio.h> #include <wchar.h> #include <string.h> int main(int argc, char **argv) { char *str = "中文"; printf("%d\n", (int)strlen(str)); return 0; }
執行結果 $ ./strlen 6 『中文』是二個字,但strlen卻 告訴我們『中文』是三個字 這是因為中文是unicode編碼非 ASCII編碼
wcslen.c #include <stdio.h> #include <wchar.h> #include <string.h> int main(int argc, char **argv) { wchar_t* wstr = L"中文"; printf("%d\n", (int)wcslen(wstr)); return 0; }
執行結果 $./wcslen 2 wchar_t* wstr = L“中文”;宣告 wstr是寬字元字串 使用wcslen(寬字元版本的 strlen)就可以正確的判斷出 字串的長度
fwide() int fwide (FILE *stream, int mode) mode為0時,回傳stream目前的讀寫狀態。大於0切換為讀寫寬字 串,小於0切換為讀寫一般字串
寬字元的讀取及寫入 #include <wchar.h> wchar_t *fgetws(wchar_t *ws, int n, FILE *stream); int fputws(const wchar_t *ws, FILE *stream); 寬字元(萬國碼,unicode)幾乎都定義在<wchar.h> 例如fgetws可以從stream中讀取一個寬字元字串
tmp file char *tempnam(const char *dir, const char *pfx); FILE *tmpfile(void); char *tmpnam(char *s); char *mktemp(char *template);
以mktemp為例 char *mktemp(char *template); 在系統中建立一個「唯一的檔案」 template的格式為「最後6個字母必須是XXXXXX(一定要大寫)」 XXXXXX會被替換成一個「唯一的字串」,確保這個檔案的檔名 在系統中是唯一 通常用來製造暫存檔案
mktemp #include <stdio.h> #include <stdlib.h> #include <errno.h> int main() { FILE* stream; char tmpStr[] = "./shiwulo_XXXXXX"; mktemp(tmpStr); printf("%s\n", tmpStr); stream = fopen(tmpStr, “w+”); /*權限為600,只有owner才可以讀寫*/ if (stream == NULL) perror("error: "); fputs("hello\0", stdout); return 0; }