gets与fgets,puts与fputs区别
一.gets與fgets
//gets函數很危險,gets沒有指定輸入字符的大小,限制輸入緩沖區的大小,如果輸入的字符大于定義的數組長度,會發生內存越界,堆棧溢出。后果嚴重!
對于 gets 函數,它的任務是從 stdin 流中讀取字符串,直至接收到換行符或 EOF 時停止,并將讀取的結果存放在 buffer 指針所指向的字符數組中。這里需要注意的是,換行符不作為讀取串的內容,讀取的換行符被轉換為 null(’\0’) 值,并由此來結束字符串。即換行符會被丟棄,然后在末尾添加 null(’\0’) 字符。其函數的原型如下:
char* gets(char* buffer);
如果讀入成功,則返回與參數 buffer 相同的指針;如果讀入過程中遇到 EOF 或發生錯誤,返回 NULL 指針。因此,在遇到返回值為 NULL 的情況,要用 ferror 或 feof 函數檢查是發生錯誤還是遇到 EOF。
函數 gets 可以無限讀取,不會判斷上限,所以程序員應該確保 buffer 的空間足夠大,以便在執行讀操作時不發生溢出。也就是說,gets 函數并不檢查緩沖區 buffer 的空間大小,事實上它也無法檢查緩沖區的空間。
如果函數的調用者提供了一個指向堆棧的指針,并且 gets 函數讀入的字符數量超過了緩沖區的空間(即發生溢出),gets 函數會將多出來的字符繼續寫入堆棧中,這樣就覆蓋了堆棧中原來的內容,破壞一個或多個不相關變量的值。如下面的示例代碼所示:
int main(void)
{
char buffer[11];
gets(buffer);
printf(“輸出: %s\n”,buffer);
return 0;
}
示例代碼的運行結果為:
aaa
輸出: aaa
根據運行結果,當用戶在鍵盤上輸入的字符個數大于緩沖區 buffer 的最大界限時,gets 函數也不會對其進行任何檢查,因此我們可以將惡意代碼多出來的數據寫入堆棧。由此可見,gets 函數是極其不安全的,可能成為病毒的入口,因為 gets 函數沒有限制輸入的字符串長度。所以我們應該使用 fgets 函數來替換 gets 函數,實際上這也是大多程序員所推薦的做法。
相對于 gets 函數,fgets 函數最大的改進就是能夠讀取指定大小的數據,從而避免 gets 函數從 stdin 接收字符串而不檢查它所復制的緩沖區空間大小導致的緩存溢出問題。當然,fgets 函數主要是為文件 I/O 而設計的(注意,不能用 fgets 函數讀取二進制文件,因為 fgets 函數會把二進制文件當成文本文件來處理,這勢必會產生亂碼等不必要的麻煩)。其中,fgets 函數的原型如下:
char *fgets(char *buf, int bufsize, FILE *stream);
該函數的第二個參數 bufsize 用來指示最大讀入字符數。如果這個參數值為 n,那么 fgets 函數就會讀取最多 n-1 個字符或者讀完一個換行符為止,在這兩者之中,最先滿足的那個條件用于結束輸入。
與 gets 函數不同的是,如果 fgets 函數讀到換行符,就會把它存儲到字符串中,而不是像 gets 函數那樣丟棄它。即給定參數 n,fgets 函數只能讀取 n-1 個字符(包括換行符)。如果有一行超過 n-1 個字符,那么 fgets 函數將返回一個不完整的行(只讀取該行的前 n-1 個字符)。但是,緩沖區總是以 null(’\0’) 字符結尾,對 fgets 函數的下一次調用會繼續讀取該行。
也就是說,每次調用時,fgets 函數都會把緩沖區的最后一個字符設為 null(’\0’),這意味著最后一個字符不能用來存放需要的數據。所以如果某一行含有 size 個字符(包括換行符),要想把這行讀入緩沖區,要把參數 n 設為 size+1,即多留一個位置存儲 null(’\0’)。
最后,它還需要第 3 個參數來說明讀取哪個文件。如果是從鍵盤上讀入數據,可以使用 stdin 作為該參數,如下面的代碼所示:
int main(void)
{
char buffer[11];
fgets(buffer,11,stdin);
printf(“輸出: %s\n”,buffer);
return 0;
}
對于上面的示例代碼,如果輸入的字符串小于或等于 10 個字符,那么程序將完整地輸出結果;如果輸入的字符串大于 10 個字符,那么程序將截斷輸入的字符串,最后只輸出前 10 個字符。示例代碼運行結果為:
aaaaaaaaaaaaaaaa
輸出: aaaaaaaaaa
除此之外,C99 還提供了 fgets 函數的寬字符版本 fgetws 函數,其函數的一般原型如下面的代碼所示:
wchar_t *fgetws(wchar_t * restrict s, int n, FILE * restrict stream);
該函數的功能與 fgets 函數一樣。
二.puts與fputs的區別:
與 gets 函數一樣,對于 puts 函數,同樣建議使用 fputs 函數來代替 puts 函數。如下面的示例代碼所示:
int main(void)
{
char buffer[11];
fgets(buffer,11,stdin);
fputs(buffer,stdout);
return 0;
}
其中,puts 函數的原型如下所示:
int puts(const char *str);
我們知道,puts 函數主要用于向標準輸出設備(屏幕)寫入字符串并換行,即自動寫一個換行符(’\n’)到標準輸出。理論上,該函數的作用與“printf("%s\n",str);”語句相同。但是,puts 函數只能輸出字符串,不能進行相關的格式變換。與此同時,它需要遇到 null(’\0’) 字符才停止輸出。因此,非字符串或無 null(’\0’) 字符的字符數組最好不要使用該函數打印,否則無法正常結束。如下面的代碼所示:
int main(void)
{
char str[] = {‘H’,‘E’,‘L’,‘L’,‘O’};
puts(str);
return 0;
}
在上面的示例代碼中,因為字符數組 str 在結尾處缺少一個 null(’\0’) 字符(也就是說它不是一個嚴格意義上的字符串)。因此,在調用 puts 函數的時候,程序將不知道什么時候停止輸出,從而導致輸出結果未定義。運行結果如下圖所示:
圖 1 示例代碼的運行結果(Microsoft Visual Studio 2010)
正確的做法是應該在字符數組 str 的結尾處添加一個 null(’\0’) 字符,如下面的示例代碼所示:
char str[] = {‘H’,‘E’,‘L’,‘L’,‘O’,’\0’};
fputs 函數的函數原型如下所示:
int fputs(const char *str, FILE *stream);
相對于 puts 函數,fputs 函數用來向指定的文件寫入一個字符串(不換行)。當然,也可以使用 stdout 作為參數進行輸出顯示(它同樣需要遇到 null(’\0’) 字符才停止輸出),如下面的代碼所示:
int main(void)
{
char str[] = {‘H’,‘E’,‘L’,‘L’,‘O’,’\0’};
fputs(str,stdout);
return 0;
}
其運行結果如下圖所示:
、
圖 2 示例代碼的運行結果(Microsoft Visual Studio 2010)
當然,fputs 函數主要用于對指定文件進行寫入操作,如下面的示例代碼所示:
int main(void)
{
FILE *fp=NULL;
fp=fopen(“myfile.txt”,“wb”);
if(fp == NULL)
{
printf(“不能夠訪問該文件.\n”);
exit(1);
}
fputs(“this is a test”, fp);
fclose(fp);
fp=NULL;
return 0;
}
運行上面的示例代碼,文件“myfile.txt”會被寫入一行“this is a test”字符串。
與 fgetws 一樣,C99 同樣也提供了 fputs 函數的寬字符版本 fputws,其函數的一般原型如下面的代碼所示:
int fputws(const wchar_t * restrict s, FILE * restrict stream);
總結
以上是生活随笔為你收集整理的gets与fgets,puts与fputs区别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: liunxC下零碎知识点的总结
- 下一篇: 约瑟夫问题的学习(基于循环链表)以及基于