Linux中國

如何在 C 語言中安全地讀取用戶輸入

getline() 提供了一種更靈活的方法,可以在不破壞系統的情況下將用戶數據讀入程序。

在 C 語言中讀取字元串是一件非常危險的事情。當讀取用戶輸入時,程序員可能會嘗試使用 C 標準庫中的 gets 函數。它的用法非常簡單:

char *gets(char *string);

gets() 從標準輸入讀取數據,然後將結果存儲在一個字元串變數中。它會返回一個指向字元串的指針,如果沒有讀取到內容,返回 NULL 值。

舉一個簡單的例子,我們可能會問用戶一個問題,然後將結果讀入字元串中:

#include <stdio.h>
#include <string.h>

int main()
{
  char city[10]; // 例如 "Chicago"

  // 這種方法很糟糕 .. 不要使用 gets

  puts("Where do you live?");
  gets(city);

  printf("<%s> is length %ldn", city, strlen(city));

  return 0;
}

輸入一個相對較短的值就可以:

Where do you live?
Chicago
<Chicago> is length 7

然而,gets() 函數非常簡單,它會天真地讀取數據,直到它認為用戶完成為止。但是它不會檢查字元串是否足夠容納用戶的輸入。輸入一個非常長的值會導致 gets() 存儲的數據超出字元串變數長度,從而導致覆蓋其他部分內存。

Where do you live?
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch
<Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch> is length 58
Segmentation fault (core dumped)

最好的情況是,覆蓋部分只會破壞程序。最壞的情況是,這會引入一個嚴重的安全漏洞,惡意用戶可以通過你的程序將任意數據插入計算機的內存中。

這就是為什麼在程序中使用 gets() 函數是危險的。使用 gets(),你無法控制程序嘗試從用戶讀取多少數據,這通常會導致緩衝區溢出。

安全的方法

fgets() 函數歷來是安全讀取字元串的推薦方法。此版本的 gets() 提供了一個安全檢查,通過僅讀取作為函數參數傳遞的特定數量的字元:

char *fgets(char *string, int size, FILE *stream);

fgets() 函數會從文件指針讀取數據,然後將數據存儲到字元串變數中,但最多只能達到 size 指定的長度。我們可以更新示常式序來測試這一點,使用 fgets() 而不是 gets()

#include <stdio.h>
#include <string.h>

int main()
{
    char city[10]; // 例如 "Chicago"

    puts("Where do you live?");

    // fgets 雖好但是並不完美
    fgets(city, 10, stdin);

    printf("<%s> is length %ldn", city, strlen(city));

    return 0;
}

如果編譯運行,你可以在提示符後輸入任意長的城市名稱。但是,程序只會讀取 size = 10 數據存儲到字元串變數中。因為 C 語言在字元串末尾會添加一個空()字元,這意味著 fgets() 只會讀取 9 個字元到字元串中。

Where do you live?
Minneapolis
<Minneapol> is length 9

雖然這肯定比 fgets() 讀取用戶輸入更安全,但代價是如果用戶輸入過長,它會「切斷」用戶輸入。

新的安全方法

更靈活的解決方案是,如果用戶輸入的數據比變數可能容納的數據多,則允許字元串讀取函數為字元串分配更多內存。根據需要調整字元串變數大小,確保程序始終有足夠的空間來存儲用戶輸入。

getline() 函數正是這樣。它從輸入流讀取輸入,例如鍵盤或文件,然後將數據存儲在字元串變數中。但與 fgets()gets() 不同,getline() 使用 realloc() 調整字元串大小,確保有足夠的內存來存儲完整輸入。

ssize_t getline(char **pstring, size_t *size, FILE *stream);

getline() 實際上是一個名為 getdelim() 的類似函數的裝飾器,它會讀取數據一直到特殊分隔符停止。本例中,getline() 使用換行符(n)作為分隔符,因為當從鍵盤或文件讀取用戶輸入時,數據行由換行符分隔。

結果證明這是一種更安全的方法讀取任意數據,一次一行。要使用 getline(),首先定義一個字元串指針並將其設置為 NULL ,表示還沒有預留內存,再定義一個 size_t 類型的「字元串大小」 的變數,並給它一個零值。當你調用 getline() 時,你需要傳入字元串和字元串大小變數的指針,以及從何處讀取數據。對於示常式序,我們可以從標準輸入中讀取:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
  char *string = NULL;
  size_t size = 0;
  ssize_t chars_read;

  // 使用 getline 讀取長字元串

  puts("Enter a really long string:");

  chars_read = getline(&string, &size, stdin);
  printf("getline returned %ldn", chars_read);

  // 檢查錯誤

  if (chars_read < 0) {
    puts("couldn&apos;t read the input");
    free(string);
    return 1;
  }

  // 列印字元串

  printf("<%s> is length %ldn", string, strlen(string));

  // 釋放字元串使用的內存

  free(string);

  return 0;
}

使用 getline() 讀取數據時,它將根據需要自動為字元串變數重新分配內存。當函數讀取一行的所有數據時,它通過指針更新字元串的大小,並返回讀取的字元數,包括分隔符。

Enter a really long string:
Supercalifragilisticexpialidocious
getline returned 35
<Supercalifragilisticexpialidocious
> is length 35

注意,字元串包含分隔符。對於 getline(),分隔符是換行符,這就是為什麼輸出中有換行符的原因。 如果你不想在字元串值中使用分隔符,可以使用另一個函數將字元串中的分隔符更改為空字元。

通過 getline(),程序員可以安全地避免 C 編程的一個常見陷阱:你永遠無法知道用戶可能會輸入哪些數據。這就是為什麼使用 gets() 不安全,而 fgets() 又太笨拙的原因。相反,getline() 提供了一種更靈活的方法,可以在不破壞系統的情況下將用戶數據讀入程序。

(題圖:MJ/4b23132f-8916-42ae-b2da-06fd2812bea8)

via: https://opensource.com/article/22/5/safely-read-user-input-getline

作者:Jim Hall 選題:lkxed 譯者:MjSeven 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國