简体   繁体   中英

Memory Allocation for a 2D Array in C

I am given the following structures to create my code with:

struct Mtrx {
       unsigned double h;
       struct MtrxRows** mtrxrows;
}
struct MtrxRows {
       unsigned double w;
       double* row;
}

I am trying to create a method called mtrxCreate that takes in parameters height and width and this is what I have below:

Mtrx* mtrxCreate(unsigned double height, unsigned double width){
       Mtrx* mtrx_ptr = malloc(sizeof(double)*height);
       int i;
       mtrx_ptr->mtrxrows = malloc(sizeof(double)*height);
       for(i = 0; i < height; ++i){
              mtrx_ptr->mtrxrows[i]->row = malloc(sizeof(double) * width);
              mtrx_ptr->mtrxrows[i]->w = width;
       }
       mtrx_ptr->h = height;
       return mtrx_ptr;
}

The GCC compiler is telling me that I have a segmentation fault so I believe I did not allocate the memory correctly. I am not sure what memory I am still needing to allocating and if I allocated the current amount to the parts of the matrix above, any help is appreciated!

You aren't allocating the right amount of memory for certain things. First of all, the Mtrx structure itself:

Mtrx* mtrx_ptr = malloc(sizeof(double)*height);

Should be:

Mtrx* mtrx_ptr = malloc(sizeof(struct Mtrx));

Next, I'm not sure why your mtrxrows field is a double pointer. I think it should be a single pointer, a one-dimensional array of rows (where each row has some number of elements in it, as well). If you change it to a single pointer, you would allocate the rows as such:

mtrx_ptr->mtrxrows = malloc(sizeof(struct MtrxRows)*height);

Edit: Sorry I keep noticing things in this sample, so I've tweaked the answer a bit.

Wow. I don't exactly know where to start with cleaning that up, so I'm going to try to start from scratch.

From your code, it seems like you want all rows and all columns to be the same size - that is, no two rows will have different sizes. If this is wrong, let me know, but it's much harder to do.

Now then, first let's define a struct to hold the number of rows, the number of columns, and the array data itself.

struct Matrix {
  size_t width;
  size_t height;
  double **data;
};

There are different ways to do store the data, but we can look at those later.

size_t is an unsigned integer (not floating point - there are no unsigned floating point types) type defined in stddef.h (among other places) to be large enough to store any valid object size or array index. Since we need to store array sizes, it's exactly what we need to store the height and width of our matrix.

double **data is a pointer to a pointer to a double , which is (in this case) a complex way to say a two-dimensional array of double s that we allocate at runtime with malloc .

Let's begin defining a function. All these lines of code go together, but I'm splitting them up to make sure you understand all the different parts.

struct Matrix *make_Matrix(size_t width, size_t height, double fill)

Notice that you have to say struct Matrix , not just Matrix . If you want to drop the struct you'd have to use a typedef , but it's not that important IMHO. The fill parameter will allow the user to specify a default value for all the elements of the matrix.

{
    struct Matrix *m = malloc(sizeof(struct Matrix));
    if(m == NULL) return NULL;

This line allocates enough memory to store a struct Matrix . If it couldn't allocate any memory, we return NULL .

    m->height = height;
    m->width  = width;
    m->data   = malloc(sizeof(double *) * height);
    if(m->data == NULL)
      {
        free(m);
        return NULL;
      }

All that should make sense. Since m->data is a double ** , it points to double * s, so we have to allocate a number of double * -sized objects to store in it. If we want it to be our array height, we allocate height number of double * s, that is, sizeof(double *) * height . Remember: if your pointer is a T * , you need to allocate T -sized objects.

If the allocation fails, we can't just return NULL - that would leak memory! We have to free our previously allocated but incomplete matrix before we return NULL .

    for(size_t i = 0; i < height; i++)
      {
        m->data[i] = malloc(sizeof(double) * width);
        if(m->data[i] == NULL)
          {
            for(size_t j = 0; j < i; j++) free(m->data[j]);
            free(m->data);
            free(m);
            return 0;
          }

Now we're looping over every column and allocating a row. Notice we allocate sizeof(double) * width space - since m->data[i] is a double * (we've dereferenced the double ** once), we have to allocate double s to store in that pointer.

The code to handle malloc failure is quite tricky: we have to loop back over every previously added row and free it, then free(m->data) , then free(m) , then return NULL . You have to free everything in reverse order, because if you free m first then you don't have access to all of m s data (and you have to free all of that or else you leak memory).

        for(size_t j = 0; j < width; j++) m->data[i][j] = fill;

This loops through all the elements of the row and fills them with the fill value. Not too bad compared to the above.

      }
    return m;
}

Once all that is done, we just return the m object. Users can now access m->data[1][2] and get the item in column 2, row 3. But before we're finished, since it took so much effort to create, this object will take a little effort to clean up when we're done. Let's make a cleanup function:

void free_Matrix(struct Matrix *m)
{
    for(size_t i = 0; i < height; i++) free(m->data[i]);
    free(m->data);
    free(m);
}

This is doing (basically) what we had to do in case of allocation failure in the (let's go ahead and call it a) constructor, so if you get all that this should be cake.

It should be noted that this is not necessarily the best way to implement a matrix. If you require users to call a get(matrix, i, j) function for array access instead of directly indexing the data via matrix->data[i][j] , you can condense the (complex) double ** allocation into a flat array, and manually perform the indexing via multiplication in your access functions. If you have C99 (or are willing to jump through some hoops for C89 support) you can even make the flat matrix data a part of your struct Matrix object allocation with a flexible array member, thus allowing you to deallocate your object with a single call to free . But if you understand how the above works, you should be well on your way to implementing either of those solutions.

As noted by @Chris Lutz, it's easier to start from scratch. As you can see from the other answers, you should normally use an integer type (eg size_t ) to specify array lengths, and you should allocate not only the pointers, but also the structures where they are stored. And one more thing: you should always check the result of allocation (if malloc returned NULL ). Always.

An idea: store 2D array in a 1D array

What I'd like to add: often it is much better to store entire matrix as a contiguous block of elements , and do just one array allocation. So the matrix structure becomes something like this:

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

/* allocate a single contiguous block of elements */
typedef struct c_matrix_t {
    size_t w;
    size_t h;
    double *elems;  /* contiguos block, row-major order */
} c_matrix;

The benefits are:

  • you have to allocate memory only once (generally, a slow and unpredictable operation)
  • it's easier to handle allocation errors (you do not need to free all previously allocated rows if the last row is not allocated, you have only one pointer to check)
  • you get a contuguous memory block, which may help writing some matrix algorithms effectively

Probably, it is also faster (but this should be tested first).

The drawbacks:

  • you cannot use m[i][j] notation, and have to use special access functions (see get and set below).

Get/set elements

Here they are, the function to manipulate such a matrix:

/* get an element pointer by row and column numbers */
double* getp(c_matrix *m, size_t const row, size_t const col) {
    return (m->elems + m->w*row + col);
}

/* access elements by row and column numbers */
double get(c_matrix *m, size_t const row, size_t const col) {
    return *getp(m, row, col);
}

/* set elements by row and column numbers */
void set(c_matrix *m, size_t const row, size_t const col, double const val) {
    *getp(m, row, col) = val;
}

Memory allocation

Now see how you can allocate it, please note how much simpler this allocation method is:

/* allocate a matrix filled with zeros */
c_matrix *alloc_c_matrix(size_t const w, size_t const h) {
    double *pelems = NULL;
    c_matrix *pm = malloc(sizeof(c_matrix));
    if (pm) {
        pm->w = w;
        pm->h = h;
        pelems = calloc(w*h, sizeof(double));
        if (!pelems) {
            free(pm); pm = NULL;
            return NULL;
        }
        pm->elems = pelems;
        return pm;
    }
    return NULL;
}

We allocate a matrix structure first ( pm ), and if this allocation is successful, we allocate an array of elements ( pelem ). As the last allocation may also fail, we have to rollback all the allocation we already made to this point. Fortunately, with this approach there is only one of them ( pm ).

Finally, we have to write a function to free the matrix.

/* free matrix memory */
void free_c_matrix(c_matrix *m) {
    if (m) {
    free(m->elems) ; m->elems = NULL;
    free(m); m = NULL;
    }
}

As the original free (3) doesn't take any action when it receives a NULL pointer, so neither our free_c_matrix .

Test

Now we can test the matrix:

int main(int argc, char *argv[]) {
    c_matrix *m;
    int i, j;
    m = alloc_c_matrix(10,10);
    for (i = 0; i < 10; i++) {
    for (j = 0; j < 10; j++) {
        set(m, i, j, i*10+j);
    }
    }
    for (i = 0; i < 10; i++) {
    for (j = 0; j < 10; j++) {
        printf("%4.1f\t", get(m, i, j));
    }
    printf("\n");
    }
    free_c_matrix(m);
    return 0;
}

It works. We can even run it through Valgrind memory checker and see, that it seems to be OK. No memory leaks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM