tadm123 tadm123 - 3 months ago 20
C Question

(C) Using Arrays with Dynamic memory allocation

I want to create a simple array of integers having 10 elements.
I'm using dynamic memory to allocate a space in memory and whenever I exceed that amount It will call realloc to double its size.
Whenever I type 'q' it will exit the loop and print the array.

I know my program is full of bugs, so please guide me into where I'm going wrong.

/* Simple example of dynamic memory allocation */

/* When array reaches 10 elements, it calls
realloc to double the memory space available */

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

#define SIZE_ARRAY 10
int main()
{
int *a;
int length=0,i=0,ch;


a= calloc(SIZE_ARRAY, sizeof(int));
if(a == NULL)
{
printf("Not enough space. Allocation failed.\n");
exit(0);
}

printf("Fill up your array: ");

while(1)
{

scanf("%d",&ch);
if(ch == 'q') //possible mistake here
break;

a[length]=ch;
length++;

if(length % 10 == 0) //when length is 10, 20, 30 ..
{
printf("Calling realloc to double size.\n");

a=realloc(a, 2*SIZE_ARRAY*sizeof(int));
}


}

printf("You array is: \n");
for(;i<length;i++)
printf("%d ",a[i]);

return 0;
}


Whenever I type 'q' the program crashes. I'm a beginner so I know I'm doing a dumb mistake. Any help would be greatly appreciated.

Answer

You should not double the memory every realloc() that can get very large, very quick. You normally expand the memory by small chunks only. realloc() also has the nasty habit to use another part of the memory if the old one cannot be elongated enough. If that fails you loose all of your data in the old memory. This can be avoided by using a temporary pointer to point to the new memory and swap them after a successful allocation. That comes with the cost of just on extra pointer (mostly 4 or 8 bytes) and a swap (needing only a handful of CPU cycles at most. Be careful with x86's xchg it uses a lock in case of multiple processors which is quite expensive!)

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

// would normally be some small power of two, like
// e.g.: 64 or 256
#define REALLOC_GROW 10
int main()
{
  // a needs to be NULL to avoid a first malloc()
  int *a = NULL, *cp;
  // to avoid complications allocated is int instead of size_t
  int allocated = 0, length = 0, i, ch, r;

  printf("Fill up your array: ");
  while (1) {
    // Will also do the first allocation when allocated == length
    if (allocated <= length) {
      // realloc() might choose another chunk of memory, so
      // it is safer to work on copy here, such that nothing is lost
      cp = realloc(a, (allocated + REALLOC_GROW) * sizeof(int));
      if (cp == NULL) {
        fprintf(stderr, "Malloc failed\n");
        // but we can still use the old data
        for (i = 0; i < length; i++) {
          printf("%d ", a[i]);
        }
        // that we still have the old data means that we need to
        // free that memory, too
        free(a);
        exit(EXIT_FAILURE);
      }
      a = cp;
      // don't forget to keep the amount of memory we've just allocated
      allocated += REALLOC_GROW;
    }
    // out, if user typed in anything but an integer
    if ((r = scanf("%d", &ch)) != 1) {
      break;
    }
    a[length] = ch;
    length++;
  }

  printf("Your array is: \n");
  // keep informations together, set i=0 in the loop
  for (i = 0; i < length; i++) {
    printf("%d ", a[i]);
  }
  fputc('\n', stdout);
  // clean up
  free(a);

  exit(EXIT_SUCCESS);
}

If you play around a bit with the start-value of allocated, the value of REALLOC_GROW and use multiplication instead of addition in the realloc() and replace the if(allocated <= length) with if(1) you can trigger the no-memory error and see if it still prints what you typed in before. Now change the realloc-on-copy by using a directly and see if it prints the data. That still might be the case but it is not guaranteed anymore.