How to properly reallocate a two-dimensional array in C?

Pastx picture Pastx · Nov 17, 2013 · Viewed 28k times · Source

I am trying to load two double numbers from input into a two-dimensional array that is dynamically reallocated by each user input.

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


int main(int argc, char** argv) {

    int count;
    double number1, number2, **numbers;

    while (scanf("%lf,%lf", number1, number2) != EOF) {

        count++;
        numbers = (double**) realloc(numbers, count * 2 * sizeof (double));
        if (numbers == NULL) {
            exit(1);
        }
        numbers[count][0] = number1;
        numbers[count][1] = number2;
    }

    return 0;
}

The program compiles without problems, but it fails every time I try to store a value in the array (it is likely a memory problem).

Can someone show me how to properly reallocate the two-dimensional array in my program?

Answer

Jonathan Leffler picture Jonathan Leffler · Nov 17, 2013

You have a couple of problems.

  1. You don't initialize numbers = 0; or count = 0 so you have an indeterminate value in the variable before you start the first realloc() call. That's bad news.
  2. The more major problem is that you've misunderstood the memory allocation that's needed to simulate a 2D-array.
  3. Your scanf() call is incorrect; you are not passing pointers to it.

ASCII Art

+---------+
| numbers |
+---------+
     |
     v
+------------+     +---------------+---------------+
| numbers[0] |---->| numbers[0][0] | numbers[0][1] |
+------------+     +---------------+---------------+
| numbers[1] |---->| numbers[1][0] | numbers[1][1] |
+------------+     +---------------+---------------+
| numbers[2] |---->| numbers[2][0] | numbers[2][1] |
+------------+     +---------------+---------------+

You actually need the pointer stored in numbers, the array of pointers, and the array of double. At the moment, you are not allocating the space for the array of pointers, and this is the cause of your troubles. The array of doubles can be contiguous or non-contiguous (that is, each row may be separately allocated, but within a row, the allocation must be contiguous, of course).

Working code:

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

int main(void)
{
    int count = 0;
    double number1, number2;
    double **numbers = 0;

    while (scanf("%lf,%lf", &number1, &number2) != EOF)
    {
        numbers = (double **) realloc(numbers, (count + 1) * sizeof(*numbers));
        if (numbers == NULL)
            exit(1);
        numbers[count] = (double *)malloc(2 * sizeof(double));
        if (numbers[count] == 0)
            exit(1);
        numbers[count][0] = number1;
        numbers[count][1] = number2;
        count++;
    }

    for (int i = 0; i < count; i++)
        printf("(%8.2f, %8.2f)\n", numbers[i][0], numbers[i][1]);

    for (int i = 0; i < count; i++)
        free(numbers[i]);
    free(numbers);

    return 0;
}

NB: This is still not good code. In particular, the increment-by-one-each-time mechanism in use is bad. The meme pointer = realloc(pointer, newsize); is bad too; you can't release the previously allocated memory if the allocation fails. You should use newptr = realloc(pointer, newsize); followed by a memory check before pointer = newptr;.

Input file:

12.34,23.45
34.56,45.67
56.78,67.89
78.90,89.01

Output data:

(   12.34,    23.45)
(   34.56,    45.67)
(   56.78,    67.89)
(   78.90,    89.01)

Not formally run under valgrind, but I'm confident it would be OK.


What is the best solution for saving inputs into array without knowing how many inputs I have to store ? Or maybe it is just this complicated in C compared to Java or PHP?

Except for the 'increment by one' part, this about the way it has to work in C, at least if you want to index into the result using two indexes: numbers[i][0] etc.

An alternative would be to allocate the space as you were doing (except not 'incrementing by one'), and then using an expression to index the array: double *numbers = ...; and numbers[i*2+0] and numbers[i*2+1] in your case, but in the more general case of an array with ncols columns, accessing row i and column j using numbers[i*ncols + j]. You trade the notational convenience of numbers[i][j] against the increased complication of memory allocation. (Note, too, that for this mechanism, the type of the array is double *numbers; instead of double **numbers; as it was in your code.)

The alternatives avoiding 'increment by one' typically use a doubling of the amount of space on each allocation. You can decide to do an initial allocation with malloc() and subsequently use realloc() to increase the space, or you can use just realloc() knowing that if the pointer passed in is NULL, then it will do the equivalent of malloc(). (In fact, realloc() is a complete memory allocation management package in one function; if you call it with size 0, it will free() the memory instead of allocating.) People debate whether (ab)using realloc() like that is a good idea or not. Since it is guaranteed by the C89/C90 and later versions of the C standard, it is safe enough, and it cuts out one function call, so I tend to use just realloc():

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

static void free_numbers(double **array, size_t size)
{
    for (size_t i = 0; i < size; i++)
        free(array[i]);
    free(array);
}

int main(void)
{
    int count = 0;
    double number1, number2;
    double **numbers = 0;
    double maxnum = 0;

    while (scanf("%lf,%lf", &number1, &number2) != EOF)
    {
        if (count == maxnum)
        {
            size_t newnum = (maxnum + 2) * 2;   /* 4, 12, 28, 60, ... */
            double **newptr = (double **)realloc(numbers, newnum * sizeof(*numbers));
            if (newptr == NULL)
            {
                free_numbers(numbers, count);
                exit(1);
            }
            maxnum = newnum;
            numbers = newptr;
        }
        numbers[count] = (double *)malloc(2 * sizeof(double));
        if (numbers[count] == 0)
        {
            free_numbers(numbers, count);
            exit(1);
        }
        numbers[count][0] = number1;
        numbers[count][1] = number2;
        count++;
    }

    for (int i = 0; i < count; i++)
        printf("(%8.2f, %8.2f)\n", numbers[i][0], numbers[i][1]);

    free_numbers(numbers, count);

    return 0;
}

This code was checked with valgrind without problems; all code allocated was freed. Note the use of the function free_numbers() to release the memory in the error paths. That's not critical when it is running in a main() function like here, but is definitely important when the work is done in a function that may be used by many programs.