How to read input of unknown length using fgets

lllook picture lllook · Dec 6, 2014 · Viewed 21k times · Source

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;
}

Answer

Jonathan Leffler picture Jonathan Leffler · Dec 6, 2014
#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;
}