如何在 C 语言中安全地读取用户输入
输入 安全
![](/data/attachment/album/202307/18/104358pr5ergummii3f3le.jpg)



> 
> `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 %ld\n", 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 %ld\n", city, strlen(city));

    return 0;
}

```

如果编译运行，你可以在提示符后输入任意长的城市名称。但是，程序只会读取 `size` = 10 数据存储到字符串变量中。因为 C 语言在字符串末尾会添加一个空（`\0`）字符，这意味着 `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 %ld\n", chars_read);

  // 检查错误

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

  // 打印字符串

  printf("<%s> is length %ld\n", 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](https://opensource.com/users/jim-hall) 选题：[lkxed](https://github.com/lkxed) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
Reading strings in C used to be a very dangerous thing to do. When reading input from the user, programmers might be tempted to use the `gets`

function from the C Standard Library. The usage for `gets`

is simple enough:

`char *gets(char *string);`


That is, `gets`

reads data from standard input, and stores the result in a string variable. Using `gets`

returns a pointer to the string, or the value NULL if nothing was read.

As a simple example, we might ask the user a question and read the result into a string:

```
``````
#include <stdio.h>
#include <string.h>
int
main()
{
char city[10]; // Such as "Chicago"
// this is bad .. please don't use gets
puts("Where do you live?");
gets(city);
printf("<%s> is length %ld\n", city, strlen(city));
return 0;
}
```


Entering a relatively short value with the above program works well enough:

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



However, the `gets`

function is very simple, and will naively read data until it thinks the user is finished. But `gets`

doesn't check that the string is long enough to hold the user's input. Entering a very long value will cause `gets`

to store more data than the string variable can hold, resulting in overwriting other parts of memory.

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


At best, overwriting parts of memory simply breaks the program. At worst, this introduces a critical security bug where a bad user can insert arbitrary data into the computer's memory via your program.

That's why the `gets`

function is dangerous to use in a program. Using `gets`

, you have no control over how much data your program attempts to read from the user. This often leads to buffer overflow.

# The safer way

The `fgets`

function has historically been the recommended way to read strings safely. This version of `gets`

provides a safety check by only reading up to a certain number of characters, passed as a function argument:

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


The `fgets`

function reads from the file pointer, and stores data into a string variable, but only up to the length indicated by `size`

. We can test this by updating our sample program to use `fgets`

instead of `gets`

:

```
``````
#include <stdio.h>
#include <string.h>
int
main()
{
char city[10]; // Such as “Chicago”
// fgets is better but not perfect
puts(“Where do you live?”);
fgets(city, 10, stdin);
printf("<%s> is length %ld\n", city, strlen(city));
return 0;
}
```


If you compile and run this program, you can enter an arbitrarily long city name at the prompt. However, the program will only read enough data to fit into a string variable of `size`

=10. And because C adds a null (‘\0') character to the ends of strings, that means`fgets`

will only read 9 characters into the string:

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


While this is certainly safer than using `fgets`

to read user input, it does so at the cost of "cutting off" your user's input if it is too long.

# The new safe way

A more flexible solution to reading long data is to allow the string-reading function to allocate more memory to the string, if the user entered more data than the variable might hold. By resizing the string variable as necessary, the program always has enough room to store the user's input.

The `getline`

function does exactly that. This function reads input from an input stream, such as the keyboard or a file, and stores the data in a string variable. But unlike `fgets`

and `gets`

, `getline`

resizes the string with `realloc`

to ensure there is enough memory to store the complete input.

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


The` getline`

is actually a wrapper to a similar function called `getdelim`

that reads data up to a special delimiter character. In this case, `getline`

uses a newline ('\n') as the delimiter, because when reading user input either from the keyboard or from a file, lines of data are separated by a newline character.

The result is a much safer method to read arbitrary data, one line at a time. To use `getline`

, define a string pointer and set it to NULL to indicate no memory has been set aside yet. Also define a "string size" variable of type `size_t`

and give it a zero value. When you call `getline`

, you'll use pointers to both the string and the string size variables, and indicate where to read data. For a sample program, we can read from the standard input:

```
``````
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int
main()
{
char *string = NULL;
size_t size = 0;
ssize_t chars_read;
// read a long string with getline
puts("Enter a really long string:");
chars_read = getline(&string, &size, stdin);
printf("getline returned %ld\n", chars_read);
// check for errors
if (chars_read < 0) {
puts("couldn't read the input");
free(string);
return 1;
}
// print the string
printf("<%s> is length %ld\n", string, strlen(string));
// free the memory used by string
free(string);
return 0;
}
```


As the `getline`

reads data, it will automatically reallocate more memory for the string variable as needed. When the function has read all the data from one line, it updates the size of the string via the pointer, and returns the number of characters read, including the delimiter.

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


Note that the string includes the delimiter character. For `getline`

, the delimiter is the newline, which is why the output has a line feed in there. If you don't want the delimiter in your string value, you can use another function to change the delimiter to a null character in the string.

With` getline`

, programmers can safely avoid one of the common pitfalls of C programming. You can never tell what data your user might try to enter, which is why using `gets`

is unsafe, and `fgets`

is awkward. Instead, `getline`

offers a more flexible way to read user data into your program without breaking the system.

## Comments are closed.