底層文件訪問
open系統(tǒng)調用
#include <fcntl.h> #include <sys/types.h>
#include <sys/stat.h> int open(const char *path, int oflags);
int open(const char *path, int oflags, mode_t mode);
在遵循POSIX規(guī)范的系統(tǒng)上,使用open系統(tǒng)調用并不需要包含頭文件sys/types.h和sys/stat.h,但在某些UNIX系統(tǒng)上,他們可能必不可少。
open系統(tǒng)調用建立了一條從到文件或設備的訪問路徑,該調用將得到與該文件相關聯(lián)的文件描述符(file discriptor)
任何一個進程可以同時打開的文件數(shù)目有限,通常由limits.h頭文件中的常量OPEN_MAX定義,該值與系統(tǒng)有關,且這個限制本身還受到系統(tǒng)全局性限制影響,所以一個程序未必總是可以打開這么多文件。在Linux系統(tǒng)中,這個限制可以隨著系統(tǒng)運行而調整,所以OPEN_MAX并不是一個常量。它通常一開始就被設置未256
| 參數(shù) | 解釋 |
|---|---|
| path | 準備打開的文件或者設備的名字 |
| oflags | 打開文件所采取的動作 |
| mode | 文件訪問模式 |
oflag參數(shù)包括下列值的組合(用“按位與”操作)
| 值 | 解釋 |
|---|---|
| O_APPEND | 把寫入數(shù)據(jù)追加到文件末尾 |
| O_TRUNC | 把文件長度設置為0,丟棄已有內(nèi)容 |
| O_CREAT | 如需要就按參數(shù)mode給出的訪問模式創(chuàng)建文件 |
| O_EXCL | 與O_CREAT一起使用,確保調用者創(chuàng)建出文件。open調用是一個原子操作,它只執(zhí)行一個函數(shù)調用。使用這個可選模式可以防止兩個程序同時創(chuàng)建一同一個文件。如果文件已經(jīng)存在,open調用失敗 |
當你使用帶有O_CREAT標志的open調用時,你必須使用帶3個有參數(shù)的open調用。第3個參數(shù)mode是幾個標志位按位或后得到的,這些標志在頭文件sys/stat.h中定義
| 值 | 權限 | 擁有者 |
|---|---|---|
| S_IRUSR | r | user |
| S_IWUSR | w | user |
| S_IXUSR | x | user |
| S_IRGRP | r | group |
| S_IWGRP | w | group |
| S_IXGRP | x | group |
| S_IROTH | r | other |
| S_IWOTH | w | other |
| S_IXOTH | x | other |
用戶掩碼(由umask命令設定)會影響被創(chuàng)建文件的訪問權限open調用中參數(shù)mode的值與當前用戶掩碼的反值做與操作
write系統(tǒng)調用
#include <stdio.h>
size_t write(int fd, const void *buf, size_t nbytes);
write系統(tǒng)調用把緩沖區(qū)buf中的前n個bytes寫入與文件描述符fd相關的文件中。
| 參數(shù) | 解釋 |
|---|---|
| fd | 數(shù)據(jù)目的地的文件描述符 |
| buf | 數(shù)據(jù)來源地的指針 |
| nbytes | 寫入數(shù)據(jù)字節(jié)數(shù) |
返回實際寫入的字節(jié)數(shù),返回值可能小于nbytes。如果返回0,表示未寫入數(shù)據(jù);如果返回-1,表示write調用出錯,錯誤代碼保存在全局變量errno中。
read系統(tǒng)調用
#include <unistd.h>
size_t read(int fd, void *buf, size_t nbytes);
read系統(tǒng)調用從與文件描述符fd相關聯(lián)的文件中讀入nbytes字節(jié)的數(shù)據(jù),并把它們放到buf中。
| 參數(shù) | 解釋 |
|---|---|
| fd | 數(shù)據(jù)來源文件的文件描述符 |
| buf | 數(shù)據(jù)目的地指針 |
| nbytes | 讀入數(shù)據(jù)字節(jié)數(shù) |
返回實際讀入的字節(jié)數(shù),可能會小于nbytes。如果返回0,表示未讀入任何數(shù)據(jù),已到達文件尾;返回-1表示出現(xiàn)錯誤。
close系統(tǒng)調用
#include <unistd.h>
int close(int fd);
close調用終止文件描述符fd與其對應文件之間的關聯(lián)。文件描述符被釋放并能夠重新使用。close調用成功時候返回0,出錯時返回-1。
檢查close調用的返回結果非常重要。有的文件系統(tǒng),特別使網(wǎng)絡文件系統(tǒng),可能不會在關閉文件之前報告文件寫操作中出現(xiàn)的錯誤,這是因為在執(zhí)行寫操作時,數(shù)據(jù)可能未被確認寫入
lseek系統(tǒng)調用
#include <unistd.h>
#include <sys/types.h>
off_t lseek(int fd, off_t offset, int whence);
lseek系統(tǒng)調用對文件描述符的讀寫指針位置進行設置> 參數(shù)whence定義該偏移量offset的用法,可取下列值
| 參數(shù) | 解釋 |
| ------------- |:-------------:|
|SEEK_SET|絕對位置|
|SEEK_CUR|相對于當前位置|
|SEEK_END|相對于文件尾|
fstat stat lstat系統(tǒng)調用
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
int fstat(int fd, struct stat *buf);
int stat(const char *path, struct stat *buf);
int lstat(const char *paht, struct stat *buf);
fstat系列調用返回與打開的文件描述符相關聯(lián)的文件的狀態(tài)信息,該信息將被寫入buf中
stat和lstat返回的使通過文件名查詢到的狀態(tài)信息。它們產(chǎn)生相同效果,但當文件是符號鏈接時,lstat返回的是該符號鏈接本身的信息,而stat返回的使該鏈接指向文件的信息。
標準IO庫
在標準IO庫中,與底層文件描述符對應的流(stream),它被實現(xiàn)為指向結構體FILE的指針(FILE )> 在啟動程序時,有三個文件流被自動打開的。它們是stdin stdout stderr他們與底層文件描述符0 1 2相對應,分別代表標準輸入 標準輸出 標準錯誤輸出IO函數(shù)可能存在緩沖區(qū)安全問題,應該避免使用這樣的函數(shù),或者十分謹慎地使用有安全問題的函數(shù)*
fopen函數(shù)
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
fopen打開由參數(shù)filename指定的文件,并把它與一個文件流關聯(lián)起來。> > 參數(shù)mode指定文件的打開方式,取下列字符串中的值。
| 參數(shù) | 解釋 | |||
|---|---|---|---|---|
| "rb" or "rb" | 只讀 | |||
| "w" or "wb" | 寫方式,并把文件長度截短為0 | "a" or "ab" | 寫方式,新內(nèi)容追加到文件尾 | |
| "r+" or "rb+" | 讀寫 | |||
| "w+" or "wb+" | 讀寫,文件長度截短為0 | |||
| "a+" or "ab+" | 讀寫,新內(nèi)容追加在文件尾 |
字母b表示文件是一個二進制(binary)文件fopen調用成功將會返回一個非空的FILE指針,失敗時返回NULLunix和Linux把所有文件都看成二進制文件,參數(shù)mode必須是字符串,所以總是應該使用雙引號,而不是單引號*
可用的文件流數(shù)量也是有限的。實際限制由頭文件stdio.h中的FOPEN_MAX定義,它的值至少為8,在linux系統(tǒng)中通常是16。
fread函數(shù)
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
數(shù)據(jù)從文件流stream讀到ptr指向的數(shù)據(jù)緩沖區(qū)中。fread和fwrite都是對數(shù)據(jù)記錄進行操作,size參數(shù)指定每個數(shù)據(jù)記錄的長度,計數(shù)器ntimes給出要傳輸?shù)挠涗泜€數(shù)。
返回值是成功讀到數(shù)據(jù)緩沖區(qū)里的記錄個數(shù)(不是字節(jié)數(shù))。當?shù)竭_文件尾,它的返回值可能會小于ntimes,甚至可以是0。
fwrite函數(shù)
#include<stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);
fwrite與fread有相似的接口。它從指定的數(shù)據(jù)緩沖區(qū)取出數(shù)據(jù)記錄,并把它們寫到文件流stream中。
返回值是成功寫入的記錄個數(shù)
fclose函數(shù)
#include <stdio.h>
int fclose(FILE *stream);
fclose函數(shù)關閉指定的文件流stream,使所有尚未寫出的數(shù)據(jù)寫出。如果要確保數(shù)據(jù)已經(jīng)全部寫出,就應該調用fclose函數(shù)。當程序正常結束時,會自動對所有還打開的文件流調用fclose函數(shù)。
fflush函數(shù)
#include <stdio.h>
int fflush(FILE *stream);
將文件流里所有尚未寫出的數(shù)據(jù)立刻寫出。有時在調試程序時,可以用它來確認程序正在寫數(shù)據(jù)而不是被掛起了。調用fclose函數(shù)隱含執(zhí)行了一次flush操作
fseek函數(shù)
fseek函數(shù)與lseek函數(shù)系統(tǒng)調用對應的文件流函數(shù)。它在文件流里為下次讀寫操作指定位置。offset和whence參數(shù)的含義和取值與前面的lseek系統(tǒng)調用完全一樣。但是fseek返回一個證書:0表示成功,-1表示失敗并設置errno支出錯誤。
fgetc getc getchar函數(shù)
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream); int getchar();
fgetc函數(shù)從文件流stream取出下一個字節(jié)并把它作為一個字符返回。當它到達文件尾或者出現(xiàn)錯誤時,它返回EOF(end of file)。可以通過ferror或者feof來區(qū)分> getc的作用和fgetc相同,但是它有可能被實現(xiàn)為一個宏(macro),如果這樣,stream參數(shù)就可能被計算不止一次,所以它不能有副作用。此外,不能保證能夠使用getc的地址作為函數(shù)指針
getchar函數(shù)相當于getc(stdin),它從標準輸入里讀取下一個字符。
fputc putc putchar函數(shù)
#include<stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
fputc函數(shù)把字符c寫到文件流stream中。它返回寫入的值,如果失敗則返回EOF.> putc函數(shù)作用相當于fputc,但它可能被實現(xiàn)為一個宏。
putchar函數(shù)相當于putc(c, stdout),它把單個字符寫到標準輸出。注意,putchar和getchard都是把字符當做int類型而不是插入類型。這就允許文件尾(EOF)取值-1,這是一個超出字符數(shù)字編碼范圍的值。
fgets gets函數(shù)
#include <stdio.h>
char *fgets(char *s, int n, FILE *stream);
char *gets(char *s);
fgets函數(shù)把讀到的字符寫到s指向的字符串里,知道出現(xiàn)下列情況:
- 遇到換行符
- 已經(jīng)傳輸了n-1個字符
- 到達文件尾
它會把遇到的換行符也傳遞到接收字符串,再加上一個表示結尾的空字符\0。
當調用成功時,fgets返回一個指向字符串s的指針。如果文件流已經(jīng)到達文件尾,fgets會設置這個文件流的EOF標志并返回一個空指針。如果出現(xiàn)讀錯誤,fgets返回一個空指針并設置errno。
gets函數(shù)類似于fgets函數(shù),但gets函數(shù)存在緩沖區(qū)溢出問題,不推薦使用
格式化輸入和輸出
printf fprintf sprintf snprintf函數(shù)
#include <stdio.h>
int printf(const char *format, ...);
int sprintf(char *s, const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int snprintf(char *s, size_t size, const char *format, ...);
snprintf修復了緩沖區(qū)問題,推薦使用
snprintf將可變個參數(shù)(...)按照format格式化成字符串,然后將其復制到str中(1) 如果格式化后的字符串長度 < size,則將此字符串全部復制到str中,并給其后添加一個字符串結束符('\0');
(2) 如果格式化后的字符串長度 >= size,則只將其中的(size-1)個字符復制到str中,并給其后添加一個字符串結束符('\0')返回值為欲寫入的字符串長度。
常見格式控制符:
| 控制符 | 解釋 |
|---|---|
| %d, %i | 十進制格式輸出整數(shù) |
| %o, %x | 八進制或十六進制格式輸出一個整數(shù) |
| %c | 輸出一個字符 |
| %s | 輸出字符串 |
| %f | 單精度浮點數(shù) |
| %e | 科學計數(shù)法格式輸出雙精度浮點數(shù) |
| %g | 以通用格式輸出一個雙精度浮點數(shù) |
-------#### scanf fscanf sscanf函數(shù)
#include <stdio.h> int scanf(const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int sscanf(const char *s, const char *format, ...);
scanf系列函數(shù)實現(xiàn)有漏洞,使用不夠靈活,不推薦使用
錯誤處理
本文中的許多函數(shù)和系統(tǒng)調用都可能失敗。它們會在失敗時候設置外部變量errno來指明失敗原因
#include <errno.h>
extern int errno;
你必須在函數(shù)調用失敗之后立刻對其進行檢查,你總是應該在使用它之前將它復制到另一個變量,因為它的值可能被下一個函數(shù)調用覆蓋,即使下一個函數(shù)并沒有出錯,也可能會覆蓋這個變量
| 錯誤代碼 | 解釋 |
|---|---|
| EPERM | 操作不允許 |
| ENOENT | 文件或目錄不存在 |
| EINTR | 系統(tǒng)調用被中斷 |
| EIO | IO錯誤 |
| EBUSY | 設備或資源忙碌 |
| EEXIST | 文件存在 |
| EINVAL | 無效參數(shù) |
| EMFILE | 打開文件過多 |
| ENODEV | 設備不存在 |
| EISDIR | 是一個目錄 |
| ENOTDIR | 不是一個目錄 |
以上錯誤代碼均保存在頭文件errno.h中
#include<stdio.h> int ferror(FILE *stream);
int feof(FILE *stream);
void clearerr(FILE *stream);
ferror函數(shù)測試文件流stream的錯誤標識,如果該標識被設置就返回一個非0值,否則返回0
feof函數(shù)測試一個文件流的文件尾標識,如果該標識被設置就返回非0值,否則返回0。
clearerror函數(shù)的作用是清除由stream指向的文件流的文件尾標識和錯誤標識。它無返回值,也未定義任何錯誤。
#include <string.h> char *strerror(int errnum);
strerror函數(shù)把錯誤代碼映射成一個字符串,該字符串對錯誤代碼進行解釋。
#include <stdio.h> void perror(const char *s);
perror函數(shù)把errno變量中的當前錯誤映射成一個字符串,并把它輸出到標準錯誤輸出流(stderr)。該字符串的前面加上字符串s(如果不為空),再加上一個冒號和空格。
void *指針類型
void即“無類型”,void *則為“無類型指針”,可以指向任何數(shù)據(jù)類型。反之則不然,例如:
void *p;
int *a;
p = a; //合法
a = p; //不合法
a = (int *) p; //合法
如果函數(shù)的參數(shù)可以是任意類型指針,那么應聲明其參數(shù)為void*。例如內(nèi)存操作函數(shù):
void * memcpy(void *dest,const void *src,size_t len);
從源src所指的內(nèi)存地址的起始位置開始拷貝n個字節(jié)到目標dest所指的內(nèi)存地址的起始位置中。返回指向dest
void * memset(void *buffer,int c,size_t num);
將buffer中前n個字節(jié)用c替換并返回buffer 。