Dennis Fagerblad
Dennis Fagerblad

Reputation: 41

dynamic buffer size for reading input

I am trying to create a program that will read line by line from stdin, search that line for the start and end of a given word and output all the matching words. Here is the code:

int main()
{
    char buffer[100];
    char **words = NULL;
    int word_count = 0;

    while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
        int length = strlen(buffer);
        if (buffer[length - 1] == '\n') {
            word_count = count_words(buffer, FIRSTCHAR);
            if (word_count > 0) {
                words = get_words(buffer, FIRSTCHAR, LASTCHAR);
                for (int i = 0; i < word_count; ++i) {
                    printf("%s\n", words[i]);
                    free(words[i]);
                }
                free(words);
            }
        }
    }
    return 0;
}

I got the basic functionality working, but I am relying on fgets() with a fixed buffer size. What I would like is to dynamically allocate a memory buffer with a size based on the length of each line.

I can only see one way of going about solving it, which is to iterate over input with fgetc and increment a counter until end of line and use that counter in place of sizeof(buffer), but I don't know how I would get fgetc to read the correct relevant line.

Is there any smart way of solving this?

Upvotes: 4

Views: 8349

Answers (2)

user3629249
user3629249

Reputation: 16540

the function: getline() does just what you want. The syntax:

ssize_t getline(char **lineptr, size_t *n, FILE *stream);

The function is exposed in the stdio.h header file and usually requires something like: #define _POSIX_C_SOURCE 200809L or #define _GNU_SOURCE as the first line in the file that calls getline()

Strongly suggest reading/understanding the MAN page for `getline() for all the grubby details.

Upvotes: 0

Pablo
Pablo

Reputation: 13580

but I am relying on fgets() with a fixed buffer size. What I would like is to dynamically allocate a memory buffer with a size based on the length of each line

I did wrote a version of fgets for another SO answer that reads the whole line and returns a malloc allocated pointer with the contents of the whole line. This is the code:

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

char *fgets_long(FILE *fp)
{
    size_t size = 0, currlen = 0;
    char line[1024];
    char *ret = NULL, *tmp;

    while(fgets(line, sizeof line, fp))
    {
        int wholeline = 0;
        size_t len = strlen(line);

        if(line[len - 1] == '\n')
        {
            line[len-- - 1] = 0;
            wholeline = 1;
        }

        if(currlen + len >= size)
        {
            // we need more space in the buffer
            size += (sizeof line) - (size ? 1 : 0);
            tmp = realloc(ret, size);
            if(tmp == NULL)
                break; // return all we've got so far
            ret = tmp;
        }

        memcpy(ret + currlen, line, len + 1);
        currlen += len;

        if(wholeline)
            break;
    }

    if(ret)
    {
        tmp = realloc(ret, currlen + 1);
        if(tmp)
            ret = tmp;
    }

    return ret;
}

The trick is to check if the newline was read. If it was read, then you can return the buffer, otherwise it reallocates the buffer with sizeof line more bytes and appends it to the buffer. You could use this function if you like.

An alternative would be if you are using a POSIX system and/or are compiling with GNU GCC, then you can use getline as well.

void foo(FILE *fp)
{
    char *line = NULL;
    size_t len = 0;

    if(getline(&line, &len, fp) < 0)
    {
        free(line); // man page says even on failure you should free
        fprintf(stderr, "could not read whole line\n");
        return;
    }

    printf("The whole line is: '%s'\n", line);

    free(line);

    return;
}

Upvotes: 5

Related Questions