How to read input of unknown length using fgets

2019-03-27 19:19发布

问题:

How am I supposed to read long input using fgets(), I don't quite get it.

I wrote this

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

int main()
{
    char buffer[10];
    char *input;
    while (fgets(buffer,10,stdin)){
        input = malloc(strlen(buffer)*sizeof(char));
        strcpy(input,buffer);
    }
    printf("%s [%d]",input, (int)strlen(input));
    free(input);
    return 0;
}

回答1:

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

int main(void)
{
    char buffer[10];
    char *input = 0;
    size_t cur_len = 0;
    while (fgets(buffer, sizeof(buffer), stdin) != 0)
    {
        size_t buf_len = strlen(buffer);
        char *extra = realloc(input, buf_len + cur_len + 1);
        if (extra == 0)
            break;
        input = extra;
        strcpy(input + cur_len, buffer);
        cur_len += buf_len;
    }
    printf("%s [%d]", input, (int)strlen(input));
    free(input);
    return 0;
}

This is about the minimal set of changes that will give you the complete line of input. This grows the space by up to 9 bytes at a time; that isn't the best way to do it, but there's extra bookkeeping involved doing it the better ways (doubling the space allocated, and keeping a record of how much is allocated vs how much is in use). Note that cur_len record the length of the string in the space pointed to by input excluding the terminal null. Also note that the use of extra avoids a memory leak on failure to allocate.

The strcpy() operation could be legitimately replaced by memmove(input + cur_len, buffer, buf_len + 1) (and in this context, you could use memcpy() instead of memmove(), but it doesn't always work while memmove() does always work, so it is more reliable to use memmove()).


With length-doubling — the cur_max variable records how much space is allocated, and cur_len records how much space is in use.

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

int main(void)
{
    char buffer[10];
    char *input = 0;
    size_t cur_len = 0;
    size_t cur_max = 0;
    while (fgets(buffer, sizeof(buffer), stdin) != 0)
    {
        size_t buf_len = strlen(buffer);
        if (cur_len + buf_len + 1 > cur_max)
        {
            size_t new_len = cur_max * 2 + 1;
            if (buf_len + 1 > new_len)
                new_len = buf_len + 1;
            char *extra = realloc(input, new_len);
            if (extra == 0)
                break;
            input = extra;
            cur_max = new_len;
        }
        strcpy(input + cur_len, buffer);
        cur_len += buf_len;
    }
    printf("%s [%d]", input, (int)strlen(input));
    free(input);
    return 0;
}


回答2:

A better approach is to use an input mechanism that will allocate for you such as getline (or even scanf). (Note: scanf does not allocate in all compilers. It does in gcc/Linux, but does not with Windows/Codeblocks/gcc)

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

int main()
{
    char *input;
    scanf ("%m[^\n]%*c", &input);
    printf("\n %s [%d]\n\n",input, (int)strlen(input));
    free(input);
    return 0;
}

output:

$ ./bin/scanfinput
This is my longer string.

 This is my longer string. [25]

getline example

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

int main()
{
    char *input = NULL;     /* input buffer, NULL forces getline to allocate */
    size_t n = 0;           /* maximum characters to read (0 - no limit      */
    ssize_t nchr = 0;       /* number of characters actually read            */

    if ((nchr = getline (&input, &n, stdin)) != -1)
        input[--nchr] = 0;  /* strip newline */

    printf ("\n %s [%zd]\n\n", input, nchr);
    free(input);

    return 0;
}