如何使用fgets()来避免转换第二个int类型的参数?

时间:2016-07-18 10:39:45

标签: c casting

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的大小而导致某些信息丢失。

也许我在这里错过了一些东西......

我应该怎样避免这种情况?

4 个答案:

答案 0 :(得分:5)

#include <stdio.h>
char *fgets(char * restrict s, int n, FILE * restrict stream);

使用fgets()时,无法使用输入缓冲区s[INT_MAX]及更高版本。

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

不是使用(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);
}

如果代码确实需要读取 long 行,请将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?

进行了讨论

答案 1 :(得分: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;
}

答案 2 :(得分:1)

我根本不打算将参数的维度与fgets()的维度相关联。

相反,我确保用于读取的缓冲区具有可以使用buffer表示的长度(例如,不超过INT_MAX)。如果您想要真正可移植,请确保缓冲区长度不超过int(标准指定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)的{​​{1}}始终可以安全地转换为{{1} }}。因此,如果您想要关闭编译器发出警告,您可以安全地转换ie size_t

答案 3 :(得分:0)

Draft

  

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

是实现定义的。因此,如果您希望将unsigned类型的值安全地转换为signed,则必须确保所有可能的源值都可以使用target,signed type来表示。