- 今天幫同學調bug的時候,遇到一個很神奇的問題
- 覺得特別有意思,在此記錄下來
我們最近在學習Linux文件編程,需要用到open()函數、write()函數等等
當fd、open()、write()這仨東西湊到一起,一不留神就會有神奇效果
1 預備知識
要想看懂這個奇妙的過程,你可能需要先了解一下這些函數
1.1 文件描述符
對於Linux而言,所有對設備和文件的操作都使用文件描述符來進行,文件描述符就相當於文件的身份證。文件描述符是一個非負的整數,表示爲int
類型的對象。它是一個索引值,指向內核中每個進程打開文件的記錄表。當打開一個現存文件或創建的一個新文件時,內核就向進程返回一個文件描述符。當需要讀寫文件時,也需要把文件描述符傳遞給相應的函數。
文件描述表的前三項對於一般的進程而言是固定的且由系統自動打開。文件描述符0
是標準輸入文件,對於一般進程來說是鍵盤;文件描述符1
是標準輸出文件,一般是顯示器;文件描述符2
是標準錯誤輸出文件,一般也是輸出到屏幕。[1]
1.2 open函數
調用open函數可以打開或創建一個文件,open是進程存取文件數據時,必須首先完成的系統調用。[1]
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char* pathname, int flags, .../* mode_t mode */);
// 返回值:成功時返回被打開文件的文件描述符 fd;失敗時返回 -1
表1.open函數的參數
參數 | 含義 |
---|---|
pathname | 表示要打開(或創建)的文件名或含路徑的文件名 |
flags | 表示打開文件的方式,具體方式見下表 |
mode | 當flags爲O_CREAT時,該項表示創建文件的權限 |
表2. flags參數的取值[2]
flags 參數 | 含義 |
---|---|
O_RDONLY | 只讀模式 |
O_WRONLY | 只寫模式 |
O_RDWR | 可讀可寫 |
O_APPEND | 表示追加,如果原來文件裏面有內容,則這次寫入會寫在文件的最末尾 |
O_CREAT | 表示如果指定文件不存在,則創建這個文件 |
O_EXCL | 表示如果要創建的文件已存在,則出錯,同時返回 -1,並且修改 errno 的值 |
O_TRUNC | 表示截斷,如果文件存在,並且以只寫、讀寫方式打開,則將其長度截斷爲0 |
表3. mode參數的取值[3]
mode參數 | 含義 |
---|---|
S_IRWXU00700 權限 | 代表該文件所有者具有可讀、可寫及可執行的權限 |
S_IRUSR 或S_IREAD,00400權限 | 代表該文件所有者具有可讀取的權限 |
S_IWUSR 或S_IWRITE,00200 權限 | 代表該文件所有者具有可寫入的權限 |
S_IXUSR 或S_IEXEC,00100 權限 | 代表該文件所有者具有可執行的權限 |
S_IRWXG 00070權限 | 代表該文件用戶組具有可讀、可寫及可執行的權限 |
S_IRGRP 00040 權限 | 代表該文件用戶組具有可讀的權限 |
S_IWGRP 00020權限 | 代表該文件用戶組具有可寫入的權限 |
S_IXGRP 00010 權限 | 代表該文件用戶組具有可執行的權限 |
S_IRWXO 00007權限 | 代表其他用戶具有可讀、可寫及可執行的權限 |
S_IROTH 00004 權限 | 代表其他用戶具有可讀的權限 |
S_IWOTH 00002權限 | 代表其他用戶具有可寫入的權限 |
S_IXOTH 00001 權限 | 代表其他用戶具有可執行的權限 |
1.3 write函數
write函數是將內存中的數據寫入文件中,其格式聲明如下:
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);
// 返回值:成功時返回寫入的字節數(若爲0表示沒有數據寫入);失敗時返回 -1
它的功能是將buf所指內存中的count個字節寫入文件描述符fd所指的文件中。具體參數含義如下表4:
表4. write函數的參數
參數 | 含義 |
---|---|
fd | 表示文件描述符 |
buf | 表示輸出緩衝區的地址指針 |
count | 表示要寫入的字節數 |
2 奇妙的組合
相信你已經瞭解這些基本知識了,下面來看看這個奇妙的組合。
這個奇妙的效果來自於一個程序,是這樣的:
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
#define NAME "save.c"
int main()
{
int fd;
char buf[100]="";
if(fd=open(NAME,O_CREAT|O_RDWR,0666)==-1)
{
perror("fail to open the file\n");
return -1;
}
printf("Please input the Letter\n" );
scanf("%s",buf);
write(fd,buf,strlen(buf));
close(fd);
return 0;
}
上面的代碼摘取自我同學的程序,程序的目的是:創建一個新文件,然後從鍵盤接收用戶輸入的字符串,將字符串寫入該文件。
乍看上去沒什麼問題,但是測試時會出現以下情況:
可以看到,在用戶輸入數據後,會有一次數據的屏幕回顯現象,數據被輸出到了屏幕上。
但是,在程序裏我們確實沒有寫任何的輸出語句啊。。。😂 怎麼會回顯到了屏幕上呢?
而且,本來應該寫入到文件中的數據,也沒有被寫入到文件中。。。
3 問題解釋
經過仔細的檢查,發現了問題所在:
以下是見證奇蹟時刻
我一直以爲是write()
函數出現了問題,沒想到其實是if語句的問題
在if
語句裏,缺少了一個括號(可以仔細看一下)。這樣的話,由於C語言賦值符號優先級較低,所以open
函數會先與==
號結合,導致文件描述符的錯誤賦值。
在正確打開文件的情況下,if(fd=open(NAME,O_CREAT|O_RDWR,0666)==-1)
這句將fd
賦值爲0
在打開文件失敗的情況下,if(fd=open(NAME,O_CREAT|O_RDWR,0666)==-1)
這句將fd
賦值爲1
很巧的是,0
和1
在文件描述符中,分別表示標準輸入流(stdin
)和標準輸出流(stdout
)
文件 | 文件描述符 |
---|---|
標準輸入 | 0 |
標準輸出 | 1 |
標準錯誤 | 2 |
因此,在正確打開文件的情況下,fd
被賦值爲0
。之後調用write
函數時,就把buf
裏的數據輸入到了stdin
,在輸入緩衝區被刷新時,數據被輸出到了屏幕,因此就會有數據的回顯效果。
同理,在錯誤打開文件的情況下,write
函數也會將數據錯誤地輸出到stdout
。
若想獲得正確的結果,需要添加括號改變運算順序,改爲下面的語句即可:
if((fd=open(NAME,O_CREAT|O_RDWR,0666))==-1)
4 總結
所以說,C語言的優先級和結合律也是非常重要的問題。編程時一定要注意細節,否則會出現意想不到的問題。
哈哈哈,挺好玩的。
5 參考資料
[1]. 《Linux C編程從入門到精通》第三章 Linux下的文件編程 - 劉學勇編著 - ISBN 978-7-121-17415-5
[2]. 【Linux】open函數的參數和作用 - ArchyLi - CSDN博客
[3]. Linux編程下open()函數的用法 - 魏波- CSDN博客