tags:

views:

3217

answers:

7

I don't truly understand some basic things in C like dynamically allocating array of arrays. I know you can do:

int **m;

in order to declare a 2 dimensional array (which subsequently would be allocated using some *alloc function). Also it can be "easily" accessed by doing *(*(m + line) + column). But how should I assign a value to an element from that array? Using gcc the following statement m[line][column] = 12; fails with a segmentation fault.

Any article/docs will be appreciated. :-)

A: 

Using malloc(3) for allocate the first array and putting in there pointers created by malloc(3) should work with array[r][c] because it should be equivalent to *(*(array + r) + c), it is in the C standard.

Keltia
+14  A: 

The m[line][column] = 12 syntax is ok (provided line and column are in range).

However, you didn't write the code you use to allocate it, so it's hard to get whether it is wrong or right. It should be something along the lines of

m = (int**)malloc(nlines * sizeof(int*));

for(i = 0; i < nlines; i++)
  m[i] = (int*)malloc(ncolumns * sizeof(int));

Some side-notes:

  • This way, you can allocate each line with a different length (eg. a triangular array)
  • You can realloc() or free() an individual line later while using the array
  • You must free() every line, when you free() the entire array
jpalecek
@jpalecek: The way I allocated memory for that was: m = malloc(lines * columns * sizeof(int *));
hyperboreean
@hyperboreean: That would allocate a one dimensional array of pointers. What you want is an array of pointers that each point to another array. You need to first allocate the array of pointers, then allocate memory for each array that is being pointed to.
yjerem
Wow, two pathetically useless edits. @chakrit: casting malloc is generally considered bad practice in C as it is not necessary and can mask errors. +1 for the original answer.
Robert Gamble
@Robert, it unfortunately depends on the compiler implementation. Some older compilers have non-standard mallocs that require a cast. VisualStudio was this way until fairly recently (can't remember the exact version)
JaredPar
@JaredPar: Any standard-conforming compiler will not require you to cast the return value of malloc. I find it difficult to believe that Visual Studio ever required this for C code (you aren't confusing C with C++ are you?).
Robert Gamble
Casting is good practice when it eliminates a warning and shows intent. Some of us use older compilers. I use MSVC 6, and that case is always in order.
EvilTeach
Casting malloc is good practice in C++ because it is required if the variable on the LHS is not a void pointer. It is bad practice in C because the warnings it eliminates are almost always errors that need to be properly addressed and removing the warning just treats the symptom.
Robert Gamble
@EvilTeach: Regarding MSVC 6, can you compile a simple example that doesn't cast malloc and compile it as *C*, not C++, code and provide the error message you get? You will get an error if you compile it as C++ (since it forbids the implicit conversion) but you should be fine compiling it as C.
Robert Gamble
Regarding older compilers: The only compilers that wouldn't allow this are those that prior to C89. Void pointers were introduced in C89 and implicit conversion to/from void * was introduced at the same time so the idea that a malloc that returns void * that needs to be cast doesn't hold water.
Robert Gamble
@Robert: Search on Google Code: `=\s*malloc\b lang:c` -- 386,000 vs. `=\s*\([^)]+\)\s*malloc\b lang:c` -- 395,000 Conclusion: both practice are common.
J.F. Sebastian
@J.F.Sebastian: It is also common practice to not check buffer sizes when using strcat/strcpy/etc. *Common Practice* != *Good Practice*.
Robert Gamble
+4  A: 

Your syntax m[line][colummn] is correct. But in order to use a 2D array in C, you must allocate memory for it. For instance this code will allocated memory for a table of given line and column.

int** AllocateArray(int line, int column) {
  int** pArray = (int**)malloc(line*sizeof(int*));
  for ( int i = 0; i < line; i++ ) {
    pArray[i] = (int*)malloc(column*sizeof(int));
  }
  return pArray;
}

Note, I left out the error checks for malloc for brevity. A real solution should include them.

JaredPar
There are several ways to dynamically create "multidimensional arrays" in C with subtle but important differences, see http://c-faq.com/aryptr/dynmuldimary.html for details. Also, as a warning to others regarding the dangers of casting malloc: http://c-faq.com/malloc/mallocnocast.html.
Robert Gamble
+3  A: 

It's not a 2d array - it's an array of arrays - thus it needs the multiple allocations.

Douglas Leeder
Probably this was my problem that I was getting a seg fault.
hyperboreean
+1  A: 

Although I agree with the other answers, it is in most cases better to allocate the whole array at once, because malloc is pretty slow.


int **
array_new(size_t rows, size_t cols)
{
    int **array2d, **end, **cur;
    int *array;

    cur = array2d = malloc(rows * sizeof(int *));
    if (!array2d)
     return NULL;

    array = malloc(rows * cols * sizeof(int));
    if (!array)
    {
     free(array2d);
     return NULL;
    }

    end = array2d + rows;
    while (cur != end)
    {
     *cur = array;
     array += cols;
     cur++;
    }

    return array2d;
}

To free the array simply do: free(*array); free(array);

Note: this solution only works if you don't want to change the order of the rows, because you could then lose the address of the first element, which you need to free the array later.

quinmars
That's a witty solution :) Although if I wanted to allocate it at once, I would probably resort to the "array[i*cols+j]" addressing and ditch the "array2d" array.
jpalecek
+1  A: 

Humm. How about old fashion smoke and mirrors as an option?

#define ROWS  5
#define COLS 13
#define X(R, C) *(p + ((R) * ROWS) + (C))

int main(void)
{
    int *p = (int *) malloc (ROWS * COLS * sizeof(int));
    if (p != NULL)
    {
        size_t r;
        size_t c;
        for (r = 0; r < ROWS; r++)
        {
            for (c = 0; c < COLS; c++)
            {
                 X(r,c) = r * c;  /* put some silly value in that position */ 
            }
        }

        /* Then show the contents of the array */ 
        for (r = 0; r < ROWS; r++)
        {
            printf("%d ", r);   /* Show the row number */ 

            for (c = 0; c < COLS; c++)
            {
                 printf("%d", X(r,c));
            }

            printf("\n");
        }

        free(p);
    }
    else
    {
        /* issue some silly error message */ 
    }

    return 0;
}
EvilTeach
+1  A: 

Here's a modified version of quinmars' solution which only allocates a single block of memory and can be used with generic values by courtesy of void *:

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

void ** array2d(size_t rows, size_t cols, size_t value_size)
{
    size_t index_size = sizeof(void *) * rows;
    size_t store_size = value_size * rows * cols;

    char * a = malloc(index_size + store_size);
    if(!a) return NULL;

    memset(a + index_size, 0, store_size);
    for(size_t i = 0; i < rows; ++i)
        ((void **)a)[i] = a + index_size + i * cols * value_size;

    return (void **)a;
}

int printf(const char *, ...);

int main()
{
    int ** a = (int **)array2d(5, 5, sizeof(int));
    assert(a);
    a[4][3] = 42;
    printf("%i\n", a[4][3]);
    free(a);
    return 0;
}

I'm not sure if it's really safe to cast void ** to int ** (I think the standard allows for conversions to take place when converting to/from void * ?), but it works in gcc. To be on the safe side, you should replace every occurence of void * with int * ...


The following macros implement a type-safe version of the previous algorithm:

#define alloc_array2d(TYPE, ROWS, COLS) \
    calloc(sizeof(TYPE *) * ROWS + sizeof(TYPE) * ROWS * COLS, 1)

#define init_array2d(ARRAY, TYPE, ROWS, COLS) \
    do { for(int i = 0; i < ROWS; ++i) \
        ARRAY[i] = (TYPE *)(((char *)ARRAY) + sizeof(TYPE *) * ROWS + \
        i * COLS * sizeof(TYPE)); } while(0)

Use them like this:

int ** a = alloc_array2d(int, 5, 5);
init_array2d(a, int, 5, 5);
a[4][3] = 42;
Christoph