5

I want to create a function to allocate (with malloc/calloc) a matrix declared as a double pointer. I understood how a double pointer matrix works and how allocate it with malloc, but when I pass my matrix (declared in main() and initialized to NULL) my program crashes. I suppose that the error is with my allocMatrix() function because if I allocate the matrix in main all works smoothly. Thanks :-)

Main:

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

#define ROW 5
#define COL 5

int main(void) {

    int i,j, ret;
    int nRow, nCol;
    int **mat=NULL; // double pointer matrix

    nRow = 0;
    nCol = 0;

    //Insert n of row and columns
    printf("Insert n of rows and columns:\n");
    scanf("%d %d", &nRow, &nCol);

    //Functions to allocate matrix
    ret=allocMatrix(mat, nRow, nCol);
    printf("Return value: %d\n",ret);

    /*
    this code works perfect!
    mat= malloc(nRow * sizeof(int));
    i=0;
    while( i < nRow)
    {
        mat[i]=malloc(nCol * sizeof(int));
        i++;
    }
    */

    //Get Values from stdin
    i=0;
    while( i < nRow)
    {
        j=0;
        while (j < nCol)
        {
            printf("Insert value pos[%d,%d]:\n", i, j);
            scanf("%d", &mat[i][j]);
            j++;
        }
        i++;
    }

    //Print values
    i=0;
    while (i < nRow)
    {
        j=0;
        while( j < nCol)
        {
            printf("Value pos[%d,%d] is: %d \n", i, j, mat[i][j]);
            j++;
        }
        i++;
    }


    system("pause");
    return EXIT_SUCCESS;
}

allocateMatrix function:

int allocMatrix(int **matrix, int nRow, int nCol)
{
    int i;
    int ext_status;

    //Classic allocation method for matrix
    matrix= malloc( nRow * sizeof(int));

    if ( matrix != NULL)
    {
        i=0;
        while (i < nRow)
        {
            matrix[i]= malloc(nCol * sizeof(int));

            if( matrix[i] != NULL)
                ext_status= 1;
            else
                ext_status= 0;
            i++;
        }
    }
    else
        ext_status= 0;

    return ext_status;
 }
MC93
  • 791
  • 6
  • 14
CSDude
  • 51
  • 1
  • 1
  • 4

4 Answers4

15

Never use pointer-to-pointer to allocate multi-dimensional arrays. It is wide-spread but bad and incorrect practice. Doing so will not give you a true 2D array, and it will lead to slower code because of heap fragmentation. It also makes the code harder to write, read and maintain, which in turn increases the potential for memory leaks.

Instead, allocate the 2D array correctly in adjacent memory cells, like this:

int x;
int y;

// store some values in x and y here

int(*matrix)[y] = malloc (sizeof(int[x][y]));

if(matrix == NULL)
{
  // error handling here
}

matrix[i][j] = something; // do something with the matrix

free(matrix);

If you insist on keeping this code in a function, then it would be:

void* allocMatrix (int nRow, int nCol)
{
  return malloc (sizeof(int[nRow][nCol]));
}

int(*matrix)[y] = allocMatrix(x, y);

Edit: explanation of the code and array pointers.

In the line int(*matrix)[y] = malloc (sizeof(int[x][y]));, the sizeof(int[x][y]) is pretty straight-forward, it is just the size of a 2D array of ints with dimensions x*y. It is using the concept of variable-length arrays from the C99 standard, which allows array dimensions to be specified in runtime.

An array pointer is a somewhat special type in C, it is able to point to whole arrays, rather than just at the first item of the array, as a normal pointer will do. The array pointer, unlike a regular pointer, knows the size of the array.

An array pointer is written as type(*name)[size], so for example an array pointer to an array of 5 ints would be written as int(*arr_ptr)[5] = &the_array;.

When accessing the contents pointed at, an array pointer behaves just as any pointer, you access the contents of it with *. So *arr_ptr gives the array pointed at, and (*arr_ptr)[0] gives the first item of that array.

For multi-dimensional arrays, the same rules apply. Given the array int arr[x][y], an array pointer to this type will be int(*arr_ptr)[x][y] = &arr;. Accessing the contents *arr_ptr will give you a two-dimensional array, which is equivalent to an array of arrays. (*arr_ptr)[0] will therefore give the first array in the array of arrays. The usual rule for any array name when used in an expression, is that it "decays" into a pointer to the first element. Same applies here, so (*arr_ptr)[0] will therefore also be the same as a pointer to the first element in the first array. And (*arr_ptr)[0][0] will give the first element of the first array.

Now this syntax (*arr_ptr)[0][0] looks a bit hard to read; to get the first item of a 2D array, we are used at writing just arr[0][0]. So when declaring the array pointer, there is a handy trick. Instead of declaring the complete and correct array pointer: int(*matrix)[x][y], an array pointer to a 2D array of dimensions x*y, we instead declare it as int(*matrix)[y], which is an array pointer to a 1D array with dimension y. It will point at the first item in the 2D array, which is a 1D array of size y. We know that the 2D array contains x such items.

And because of this trick, we'll now be able to use the array pointer with the same syntax as when accessing a 2D array, namely matrix[i][j], rather than the hard-to-read (*matrix)[i][j].

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • 1
    I've already read about this problem of continuous memory block and I'm perfectly agreed with you, but I'm a student so I must follow my professor' guidelines (**matrix). btw I'm interested in your solution cause is the most correct. So can you explain me better what mean the syntax exactly? How it works in detail this approach? I'm talking about this: int(*matrix)[y] = malloc (sizeof(int[x][y])); – CSDude Aug 17 '15 at 15:53
  • @Lundin how to get the sizeof the the vla using sizeof operator, i tried a few permutations like `sizeof(*a)` , `sizeof(*a[x])` etc. but it doesnt come out to be what it should be. Given the fact that we have allocated just a "contiguous" chunk from memory and nothing else the memory should come out to be `sizeof(int) * x * y` – datapanda Aug 03 '18 at 14:38
3

where's written double you can change for your matrix type

void allocMatrix(double ***matrix, int row, int col){
    int i = 0;
    *matrix = (double **)malloc(sizeof(double *) * row);
    for(i = 0; i < col; i++){
        *(*matrix + i) = (double *)malloc(sizeof(double) * col);
    }
}

void deallocMatrix(double **matrix, int row){
    int i = 0;
    for(i = 0; i < row; i++){
        free(matrix[i]);
    }
    free(matrix);
}

when in main you can use them like this

int main(){
    int i = 0, j = 0, k = 0, row = 3, col = 4;
    double **myMatrix = NULL;
    allocMatrix(&myMatrix, row, col);
    for(i = 0; i < row; i++){
        for(j = 0; j < col; j++){
            myMatrix[i][j] = k++;
            printf("%.0lf\t", myMatrix[i][j]);
        }
        printf("\n");
    }
    deallocMatrix(myMatrix, row);
    return 0;
}

output

1    2    3    4
5    6    7    8
9    10   11   12

that may avoid memory leaks, but as Lundin said, its better not to use 2d arrays

Never use pointer-to-pointer to allocate multi-dimensional arrays. It is wide-spread but bad and incorrect practice. Doing so will not give you a true 2D array, and it will lead to slower code because of heap fragmentation. It also makes the code harder to write, read and maintain, which in turn increases the potential for memory leaks. Instead, allocate the 2D array correctly in adjacent memory cells

[]

0

The problem here, is you're passing mat itself to the allocator function, which cannot hold the allocated memory from the function upon return as C uses pass-by-value for function argument passing.

You need to pass a pointer to mat and allocate accordingly.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
0

You're passing the matrix to allocMatrix by value, when it should be by reference.

Define your function as int allocMatrix(int ***matrix, int nRow, int nCol) and replace matrix with *matrix (btw careful with the syntax for indexing, it must be (*matrix)[i], not *matrix[i]).

This means you will call this function as allocMatrix(&mat, nRow, nCol).

MC93
  • 791
  • 6
  • 14
  • Avoid fragmented lookup tables and three star programming in general. It is better to allocate a real 2D array instead. – Lundin Aug 17 '15 at 12:54
  • @Lundin - As always, it depends on the siuation. A contiguous block would most likely result in better performance, but your heap may be fragmented to the point where you don't have a single block large enough. And remember, VLAs are *optional* now, so VLA syntax may not be supported on all implementations going forward. – John Bode Aug 17 '15 at 15:08
  • I was suspecting that this was my problem... but when I pass my matrix to another function that fill it with a simple scanf() o random values with rand(), all it's ok (the matrix is filled when function returns). So is this the same situation, or something change from the allocation function?cause I don't understand now! thanks – CSDude Aug 17 '15 at 16:33
  • Yes, filling it with values should work fine just passing an `int **`, because you are assigning to the referenced `int` elements. But if you want changes to the double pointer itself to have effect outside the function, you need to pass a reference to it. It's like when you want a function to make a lasting change to an `int` parameter, you have to pass an `int *` and set the `int` value by dereferencing inside the function. Taking that principle up one level, if you want to set the value of an `int *` (e.g. allocate memory), you need to pass an `int **`. And if you want to set an `int **`... – MC93 Aug 17 '15 at 22:32
  • @JohnBode But the handling of fragmentation should be done by the heap alloc routines and not the programmer. VLAs may be optional, but if some compiler decides not to support them, then that compiler will not be used. Historically, compilers always support ten times more nonsense features than those provided by the language standard. Why would they suddenly stop doing that now? Especially since all decent compilers have already implemented VLAs. – Lundin Aug 18 '15 at 06:36
  • @JohnBode The only place where it doesn't make sense to use them is resource-constrained embedded systems. Yet the VLA feature enables us to declare variable-sized array pointers, even if no actual VLA exists in the source code. And even if you can't use VLAs you can still allocate memory in adjacent cells, with old school "mangled" arrays: `int* ptr = malloc(sizeof(int)*x*y);`. – Lundin Aug 18 '15 at 06:38
  • @MC93 It is still not a 2D array. Given dynamically allocated pointer to pointer mess `int** matrix`, then try `int array [x][y]; memcpy(matrix, array, sizeof(int[x][y]));` and watch your program crash & burn. – Lundin Aug 18 '15 at 06:42