【问题标题】:How to use fgets() to avoid casting its second argument which is of type int?如何使用 fgets() 来避免转换其第二个 int 类型的参数?
【发布时间】:2016-07-18 10:39:45
【问题描述】:

fgets 函数的声明如下所示:

char *fgets(char *str, int n, FILE *stream);

这意味着第二个参数应该是int

在以下程序中避免这种转换的正确方法是什么?

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

int main(void) {
    const char *buffer = "Michi";
    size_t len = strlen(buffer) + 1;
    char arr[len];

    printf("Type your Input:> ");
    if (fgets(arr, (int)len, stdin) == NULL) {
        printf("Error, fgets\n");
        exit(1);
    } else {
        printf("Arr = %s\n", arr);
    }
}

这里我使用了(int)len,看起来不错,但是如果buffer 存储了一个很长的字符串会怎样?

让我们说:

const char *buffer = "Very long long ..."; /* where length is beyond the range of the `int` type */

我已经声明了 size_t 类型的长度,这在这里很好,但是如果我将它传递给 fgets 是不行的,因为:

conversion to ‘int’ from ‘size_t {aka long unsigned int}’ may alter its value

如果我将其转换为int 将不适合,因为int 的大小小于length 的大小,并且会丢失一些信息。

也许我在这里遗漏了一些东西......

我应该如何避免这种情况?

【问题讨论】:

  • 只是说,在这种情况下,您确定char arr[len]; 会正常工作吗?无论如何,它与 gcc 堆栈..
  • @SouravGhosh 我可以在这里使用 malloc,但问题不在于那个
  • 也许stackoverflow.com/questions/8317295/…对你有帮助。
  • @Serhio 是否也包括long unsigned int
  • @Michi 我想是的。 ...对于超出范围的值在有符号/无符号之间进行转换是实现定义的...唯一的标准是超出目标类型的范围,无论 longshort 修饰符如何。

标签: c casting


【解决方案1】:
#include <stdio.h>
char *fgets(char * restrict s, int n, FILE * restrict stream);

对于fgets(),输入缓冲区s[INT_MAX] 及以上无法使用。


OP 的size_t len 代码可以通过转换为字符串并转换回int 来避免lenint 转换,这只是浪费。演员阵容是正确的做法。

与其用(int) 乱扔代码,不如减少/控制其使用并将强制转换包装在一个限制性辅助函数中。

int fgets_len(size_t len) {
  return (len < INT_MAX) ? (int) len : INT_MAX;
}


size_t len = something_big;
char *arr = malloc(len);

...   
if ( fgets(arr, fgets_len(len), stdin) == NULL){
    printf("Error, Fgets\n");
    exit(1);
}else{
    printf("Arr = '%s'\n", arr);
}

如果代码确实需要读取 行,请考虑将 ssize_t getline(char **lineptr, size_t *n, FILE *stream); 定义为 here。请注意,这是一个非标准 C 库函数,但它的源代码很容易获得。


关于fgets()的迂腐使用,fgets()返回NULL至少有两个原因:文件结束和输入错误。现在考虑使用 OP 代码的极端情况

size_t len = strlen("") + 1;
char arr[len];
if ( fgets(arr, (int)len, stdin) == NULL){

以及像

这样的病理病例
if ( fgets(arr, 0, stdin) == NULL){
if ( fgets(arr, -1, stdin) == NULL){

两者都在Is fgets() returning NULL with a short bufffer compliant?进行讨论

【讨论】:

  • 对于fgets(),输入缓冲区s[INT_MAX] 及以上不能使用。:更准确地说,可以使用任何大小的缓冲区,但如果它更长比INT_MAX 的大小不能直接使用。请注意,s[INT_MAX] 不是问题。
【解决方案2】:

如果你想避免整数溢出的麻烦,你可以做值检查并采取相应的行动。请注意,由于 strlen() 返回类型为 size_t 的值,您有 2 个选项。要么声明一个 int 类型的变量,并为其分配 strlen() 的返回值,这将执行从 size_t 类型到 int 的隐式转换,或者像你在函数调用中所做的那样进行转换。

这是一个可能的解决方案:

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

int main(void)
{
    const char *buffer = "Michi";
    //size_t len = strlen(buffer) + 1;
    size_t len = 9999999999;

    if (len > INT_MAX) {
        fprintf(stderr, "Error, length exceeded desired value.Aborting...\n");
        return 1;
    }

    char arr[len];

    printf("Type your Input:> ");
    if (fgets(arr, (int)len, stdin) == NULL) {
        printf("Error, Fgets\n");
        exit(1);
    }
    else {
        printf("Arr = %s\n", arr);
    }
    return 0;
}

【讨论】:

  • 我已经知道len &gt; INT_MAX 所以这里if (len &gt; INT_MAX) 我的程序将停止工作,这是错误的。
  • @Michi - 如果您用n &lt;= 0 调用fgets,它将返回NULL,甚至无需等待输入,因此您的程序无论如何都会终止。所以最好的办法是在去之前检查len
  • @Michi - size_tint 最终可能会得到一个积极但毫无意义的值。示例 0x100000003 可能会变成 3,这会使您的程序表现得很奇怪。所以在打电话之前检查len不超过INT_MAX
【解决方案3】:

我根本不会费心将参数的维度与fgets()buffer 的维度联系起来。

相反,我会确保用于读取的缓冲区具有可以使用int 表示的长度(例如,不超过 INT_MAX)。如果你想真正便携,确保缓冲区长度不超过32767(标准规定INT_MAX的最小允许值为32767)。

然后利用fgets() 将在行长度超过缓冲区长度时分段读取一行的事实。

例如,假设len 超过了从stdin 读取的任何行的长度;

char arr[len] = {0};   
char read_buffer[10];    /*   I'm reasonably confident that 10 < 32767 */

while (fgets(read_buffer, 10, stdin) != NULL)
{
      size_t read_length = strlen(read_buffer);
      if (read_length > 0)
      {
           if (read_buffer[read_length-1] != `\n`)
           {
               strcat(arr, read_buffer);
           }
           else
           {
               strncat(arr, read_buffer, read_length-1);

               printf("Arr = %s\n", arr);

               arr[0] = '\0';    /*  clear arr so next line may be read */

               /*  break here if want to stop reading after the first line */

           }
      }

请注意,如果文件结尾不是紧跟在'\n' 之前,那么上面将丢弃最后一个'\n' 之后的文本。

在上面,将fgets(read_buffer, 10, stdin) 替换为fgets(read_buffer, sizeof read_buffer, stdin) 是安全的,因为值小于或等于INT_MAXsize_t 始终可以安全地转换为int。所以,如果你想关闭编译器发出警告,你可以安全地转换即fgets(read_buffer, (int)(sizeof read_buffer), stdin)

【讨论】:

    【解决方案4】:

    Draft 这么说

    将整数转换为更短的有符号整数的结果,或 将无符号整数转换为有符号整数的结果 等长,如果值不能表示

    是实现定义的。因此,如果您想安全地将无符号类型的值转换为有符号 - 您必须确保所有可能的源值都可以用目标、有符号类型表示。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-18
      • 1970-01-01
      • 1970-01-01
      • 2019-10-04
      • 2014-11-26
      • 1970-01-01
      • 2019-03-02
      相关资源
      最近更新 更多