bit_by_bit
bit_by_bit

Reputation: 337

Using getline along with dynamic storage

I have the following snippet. I have come across several examples where one can read a line using getline and then simply print it. I am struggling to continuously save from stdin, read using getline() and probably have a buffer of some sort to keep track of all that is read (is it needed?).

Eventually, I just print the contents in reverse order of the user's input last line first. I am not sure if char* can point to the entire buffer os stdin input, that we can reverse read eventually.

This is running into segmentation fault, my best guess would be around accessing memory that was never allocated.

  size_t linecount = 0;
  ssize_t bytes_read;
  size_t nbytes = 100;
  char *content;

  if (1) {
    my_string = (char*) malloc(nbytes + 1);
    while ((bytes_read = getline(&my_string, &nbytes, stdin)) >= 0
        && my_string[0] != '\n') {
      puts(my_string);
      printf("read: %ld bytes", bytes_read);
      content = (char*) malloc((strlen(my_string) + 1) * sizeof(char));
      success = content != NULL;

      if (success) {
        strcpy(content, my_string);
        ++linecount;
      } else {
        printf("Malloc error\n");
        exit(1);
      }
    }
  }

Upvotes: 0

Views: 1421

Answers (2)

David C. Rankin
David C. Rankin

Reputation: 84579

Reading with getline(), if you provide a pointer initialized NULL and the size argument initialized zero, then getline() will allocate sufficient storage for your input -- regardless of the length. If you are simply reversing the string, then there is no reason to allocate additional storage. You only need to allocate additional storage if you want to save multiple lines read by getline() in a collection (such as when using an array of pointers, e.g. char *lines[NLINES];, or pointer-to-pointer, e.g. char **lines;)

To read all lines of input with getline(), after opening your files (or simply assigning stdin to the FILE* pointer), you need no more than:

    char *lineptr = NULL;       /* pointer for getline() set NULL */
    size_t n = 0;               /* n set to 0, getline() allocates */
    ...
    while (getline (&lineptr, &n, fp) != -1)    /* read every line from file */
        ...

If you are simply reversing the characters in each line, you can write a simple string reversal function and output the reversed string. The getline() read loop with a function called strrev() that takes the string as an argument would be:

    while (getline (&lineptr, &n, fp) != -1)        /* read every line from file */
        fputs (strrev (lineptr), stdout);           /* reverse line & output */

Crafting your strrev() function to handle the string regardless of whether a '\n' is present allows you to simply pass the line from getline() containing the '\n' character at the end, without having to trim the newline from the string first. If you did want to remove the '\n', you can use the number of characters returned by getline(), or a simple call to strcspn() is all that is required, e.g.

    while (getline (&lineptr, &n, fp) != -1) {  /* read every line from file */
        lineptr[strcspn (lineptr, "\n")] = 0;   /* trim '\n' from end of lineptr */
        /* do whatever else is desired */
    }

Putting it altogether, and taking the filenames to read as the first argument to the program (or reading from stdin by default if no argument is given), you could do:

#define _GNU_SOURCE             /* getline() is POSIX not standard C */

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

char *strrev (char *str);       /* function to reverse string in-place         */
                                /* (str must be mutable, not a string-literal) */

int main (int argc, char **argv) {
    
    char *lineptr = NULL;       /* pointer for getline() set NULL */
    size_t n = 0;               /* n set to 0, getline() allocates */
    
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while (getline (&lineptr, &n, fp) != -1)        /* read every line from file */
        fputs (strrev (lineptr), stdout);           /* reverse line & output */
    
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    free (lineptr);    /* getline() allocates, so don't forget to free the memory */
}

/** strrev - reverse string, swaps src & dest each iteration up to '\n'.
 *  Takes valid string and reverses, original is not preserved.
 *  If str is valid, returns pointer to str, NULL otherwise.
 */
char *strrev (char *str)
{
    if (!str) {     /* validate str not NULL */
        printf ("%s() error: invalid string\n", __func__);
        return NULL;
    }
    if (!*str)      /* check empty string */
        return str;
    
    char *begin = str,
         *end = begin + strcspn (str, "\n") - 1;

    while (end > begin)
    {
        char tmp = *end;
        *end--   = *begin;
        *begin++ = tmp;
    }

    return str;
}

Example Use/Output

Reading from stdin and using Ctrl + d when done (Ctrl + z on windows):

$ ./bin/getline_strrev
My dog has fleas
saelf sah god yM
My cat has none!
!enon sah tac yM
Lucky cat...
...tac ykcuL

Reading (or redirecting) input from a file:

Input File

$ cat dat/dog.txt
My dog has fleas
My cat has none!
Lucky cat...

Example Use/Output

$ ./bin/getline_strrev dat/dog.txt
saelf sah god yM
!enon sah tac yM
...tac ykcuL

Redirecting the file on stdin:

$ ./bin/getline_strrev < dat/dog.txt
saelf sah god yM
!enon sah tac yM
...tac ykcuL

Reversing Lines Not Characters

Given your clarification in the comments, you can store an unknown number of lines of unknown length by allocating pointers (and reallocating as required) and allocating storage for each line, assigning the address of the block to hold each line to each of your pointers in turn and then copying the line read by getline() to the allocated block. You have two types of memory to content with, the block of memory holding pointers, and the blocks of memory holding each line. This is explained in great detail in How to read a text file and store in an array in C from a day or two ago.

The only different between that example and here is the use of getline() instead of fgets() and the minor changes to how to compute the number of characters in each line and remove the ending '\n' from the buffer are trivially different, just due to getline() returning the number of characters read. Reversing the order of the lines is also different (but also a trivial difference)

The changes for getline() and reversing the lines are:

#define _GNU_SOURCE

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

#define NPTRS 2     /* initial number of pointers to allocated */

int main (int argc, char **argv) {
    
    char **lines = NULL,        /* pointer to all lines */
        *lineptr = NULL;        /* pointer for getline() set NULL */
    size_t  n = 0,              /* n set to 0, getline() allocates */
            nptrs = NPTRS,      /* no. of pointers to allocate */
            used = 0;           /* no. of pointers used */
    ssize_t nchr;               /* no. of chars read by getline() */
    
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }
    
    /* allocate initial nptrs pointers - validate EVERY allocation */
    if ((lines = malloc (nptrs * sizeof *lines)) == NULL) {
        perror ("malloc-lines");
        return 1;
    }
    
    /* read every line from file, saving characters read in nchr */
    while ((nchr = getline (&lineptr, &n, fp)) != -1) {
        if (nchr > 0 && lineptr[nchr-1] == '\n')    /* if chars read and \n */
            lineptr[--nchr] = 0;                    /* trim \n from end */
        if (used == nptrs) {
            /* always realloc using a temporary pointer */
            void *tmp = realloc (lines, (2 * nptrs) * sizeof *lines);
            if (!tmp) {                             /* validate EVERY reallocation */
                perror ("realloc-lines");
                break;                              /* don't exit, lines still good */
            }
            lines = tmp;                            /* assign reallocated block */
            nptrs *= 2;                             /* update number of pointers */
        }
        /* allocate storage for line, assign address for new block to lines[used] */
        if (!(lines[used] = malloc (nchr + 1))) {
            perror ("malloc-lines[used]");
            break;
        }
        memcpy (lines[used], lineptr, nchr + 1);    /* copy line to new block of mem */
        
        used += 1;      /* increment used pointer counter */
    }
    
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
        
    /* output lines in reverse order */
    while (used--) {
        puts (lines[used]);
        free (lines[used]);     /* free storage for each string when done */ 
    }
    free (lines);               /* free pointers */
    
    free (lineptr);             /* free memory allocated by getline() */
}

Example Use/Output

Same inpt file for example as above.

$ ./bin/getline_linerev dat/dog.txt
Lucky cat...
My cat has none!
My dog has fleas

Memory Use/Error Check

In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.

It is imperative that you use a memory error checking program to ensure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.

For Linux valgrind is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.

$ valgrind ./bin/getline_linerev dat/dog.txt
==6502== Memcheck, a memory error detector
==6502== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==6502== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==6502== Command: ./bin/getline_linerev dat/dog.txt
==6502==
Lucky cat...
My cat has none!
My dog has fleas
==6502==
==6502== HEAP SUMMARY:
==6502==     in use at exit: 0 bytes in 0 blocks
==6502==   total heap usage: 9 allocs, 9 frees, 5,887 bytes allocated
==6502==
==6502== All heap blocks were freed -- no leaks are possible
==6502==
==6502== For counts of detected and suppressed errors, rerun with: -v
==6502== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Always confirm that you have freed all memory you have allocated and that there are no memory errors.

Look things over an let me know if you have further questions or need additional help.

Upvotes: 1

Jonathan Leffler
Jonathan Leffler

Reputation: 754450

Since the goal is "print the contents in reverse order of the user's input, last line first", the program must store all the lines. The getline() function typically allocates quite a large space for each line (128 bytes by default on my Mac, and growing if input lines are longer than that), so it is usually best to have a buffer managed by getline() that can grow if need be, and to copy the actual input strings somewhere else with the requisite length. I use strdup() to copy the line.

/* Read file and print the lines in reverse order */

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

int main(void)
{
    char **ptrs = 0;
    size_t numptrs = 0;
    size_t count = 0;
    char  *buffer = 0;
    size_t buflen = 0;

    while (getline(&buffer, &buflen, stdin) != -1)
    {
        if (count == numptrs)
        {
            size_t newnum = (numptrs + 2) * 2;
            void *newptrs = realloc(ptrs, newnum * sizeof(*ptrs));
            if (newptrs == 0)
            {
                fprintf(stderr, "Out of memory (%zu bytes requested)\n", newnum * sizeof(*ptrs));
                exit(1);
            }
            ptrs = newptrs;
            numptrs = newnum;
        }
        ptrs[count++] = strdup(buffer);
    }

    free(buffer);

    /* Print lines in reverse order */
    for (size_t i = count; i > 0; i--)
        fputs(ptrs[i-1], stdout);

    /* Free allocated memory */
    for (size_t i = 0; i < count; i++)
        free(ptrs[i]);
    free(ptrs);

    return 0;
}

It's easy to argue that the code should check that strdup() succeeds and take appropriate action if it does not. The code freeing the allocated memory should be in a function — that would make clean up after an error easier, too. The code could be revised into a function that could be used to process files listed as command-line arguments instead of standard input.

Given this text as standard input:

Because it messes up the order in which people normally read text.
> Why is top-posting such a bad thing?
>> Top-posting.
>>> What is the most annoying thing in e-mail?

the program produces the output:

>>> What is the most annoying thing in e-mail?
>> Top-posting.
> Why is top-posting such a bad thing?
Because it messes up the order in which people normally read text.

Upvotes: 1

Related Questions