Part 7. Advanced programming

Changing an array's size with realloc

We learned in part 5 about pointers, and how to allocate memory for an array using malloc. Thisis all good and well if you know how big the array should be when you allocate it. But what if you need to extend the array after allocating memory for it? That's where the realloc function comes in.

Using realloc, you can change the array's size. Usually you will want to make the array bigger, but you can also make it smaller if you later decided you allocated too much memory to it.

To use realloc, you need to give a pointer to the array you want to resize, and how big the new new array should be. realloc will return a pointer to the new memory.

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

int
main()
{
   int *array;
   int size = 100;

   int *newarray;
   int newsize;

   int i;

   /* allocate the array */

   array = (int *) malloc(size * sizeof(int));

   if (array == NULL) {
      fprintf(stderr, "ERROR! The array was not allocated\n");
      return 1;
   }

   printf("the array is now %d long\n", size);

   /* store some data */

   for (i = 0; i < size; i = i + 1) {
      array[i] = 100 + i;
   }

   for (i = 0; i < size; i = i + 1) {
      printf("%d ", array[i]);
   }
   putchar('\n');

   /* increase the size of the array */

   /* realloc returns a ptr to the new array. If reallocating memory
      failed, then the array ptr (argument to realloc) is not touched,
      and realloc returns NULL. */

   newsize = size + 50;

   newarray = (int *) realloc(array, newsize * sizeof(int));

   if (newarray == NULL) {
      fprintf(stderr,
              "ERROR! The array could not be resized - but the old array pointer is ok\n");
   }
   else {
      /* reassign the pointer so we can keep using the old name */
      array = newarray;
      size = newsize;
   }

   printf("the array is now %d long\n", size);

   /* store some data */

   for (i = 0; i < size; i = i + 1) {
      array[i] = 1000 + i;
   }

   for (i = 0; i < size; i = i + 1) {
      printf("%d ", array[i]);
   }
   putchar('\n');

   /* free the array */

   free(array);

   puts("the array was freed");

   return 0;
}

If realloc has enough memory for the array, it will return with a pointer to the new memory. Otherwise, realloc will return a NULL value to indicate an error.

Reading long strings

This ability to increase an array's size lends itself to reading user input—especially strings. Because we cannot always trust that the user will input only reasonable values, programmers often write a function that reads a long string from the user, and updates the size of the string as they go. To paraphrase an old programmer's saying: “where you expect someone to type foo into a string that's only ten characters long, someone will try to type supercalifragilisticexpialidocious.”

Let's write an implementation of this function, just to show one way to write it. A simple implementation might read input one character at a time. When the function has read as much data as the string will hold, it can call realloc to increase the string's size. And so on until it reads a newline character, indicating the user has finished typing a line of data.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>                    /* strcpy */

#define GETLONGSTR_SIZE_INCR 100

int
getlongstr(char **string, int *stringsize, FILE * input)
{
   char *str;
   char *newstr;
   int strsize;
   int newsize;
   int nchars = 0;
   int ch;

   /* check if we need to allocate the string */

   if ((*string == NULL) || (stringsize == 0)) {
      fprintf(stderr, "[malloc]");     /* debugging */
      strsize = GETLONGSTR_SIZE_INCR;
      str = (char *) malloc(strsize * sizeof(char));

      if (str == NULL) {
         /* error */
         return -1;
      }
   }
   else {
      strsize = *stringsize;
      str = *string;
   }

   /* read from input until we reach newline or end of file */

   while (((ch = fgetc(input)) != '\n') && (ch != EOF)) {
      /* add to the string */
      str[nchars] = ch;

      /* do we need to make the string bigger? */
      if (nchars == strsize) {
         fprintf(stderr, "[realloc]"); /* debugging */
         newsize = strsize + GETLONGSTR_SIZE_INCR;
         newstr = (char *) realloc(str, newsize * sizeof(char));

         if (newstr == NULL) {
            /* error in realloc - return what we have */
            *string = str;
            *stringsize = strsize;
            return nchars;
         }

         str = newstr;
         strsize = newsize;
      }

      nchars = nchars + 1;
   }

   /* insert a null terminator (nchars is already correct) */

   str[nchars] = '\0';

   /* done */

   *string = str;
   *stringsize = strsize;
   return nchars;
}

int
main()
{
   char *string = NULL;
   int size = 0;
   int nchars;

   /* read a long line */

   puts("enter your name:");
   nchars = getlongstr(&string, &size, stdin);

   if (nchars < 0) {
      fprintf(stderr, "**error**\n");
      free(string);
      return 1;
   }

   /* print the string */

   printf("read %d chars\n", nchars);
   printf("string is %d size\n", size);
   printf("the string was <%s>\n", string);

   /* done */

   free(string);
   return 0;
}

This getlongstr function does a lot of pointer assignment, and it can be easy to get lost in all the pointers. The important thing to note is the general flow of the function: it reads one character at a time, and increases the string with realloc if the user types too much data. At the end, the getlongstr function reassigns the string so the calling function can access the data.

This kind of function to read arbitrarily long strings is so useful that in 2010, the group that maintains the C programming language added a getline function to do the same thing. You should check if your C compiler includes this function, and use that in place of gets or fgets when you need to read data from the user.

If you don't have a getline function in your compiler, you can download getline.c from FreeBSD (Shawn Webb's GitHub).

Here's a sample program that demonstrates how to use the getline function. getline reads everything up to the first newline, and the string it returns includes that newline. If you don't want the newline, you can replace it with a null value. In this sample program, I've included a trch_strn that swaps one character for another in a string of length n. Specifically, this program uses trch_strn to replace the newline with a null.

#include <stdio.h>                     /* getline */
#include <stdlib.h>                    /* free */

ssize_t getdelim(char **buf, size_t *bufsiz, int delimiter, FILE *fp);

int
trch_strn(char c1, char c2, char *str, size_t str_len)
{
   int i = 0;
   int nchanged = 0;

   /* find occurrences of c1 and replace with c2 */

   while (i < str_len) {
      if (str[i] == c1) {
         str[i] = c2;
      }

      i = i + 1;
   }

   return nchanged;
}

int
main()
{
   char *string = NULL;
   size_t string_size = 0;
   size_t nchars;

   /* read a long string */

   puts("Enter a really long string:");

   nchars = getline(&string, &string_size, stdin);

   /* check if an error */

   if (nchars < 1) {
      fprintf(stderr, "error on getline\n");
      free(string);
      return 1;
   }

   /* print the string */

   printf("read %u chars\n", nchars);
   printf("string is %u size\n", string_size);

   trch_strn('\n', '\0', string, string_size);
   printf("the string was: <%s>\n", string);

   /* free the string */

   free(string);

   return 0;
}