0

In trying to understand pointers, I created a M[x][y] array, a pointer to said array *p_M[x] and a pointer to said pointer **d_p_M. The first pointer points to the first element of a row in the array M, and the double pointer points to the first row in *p_M.

Inside the function the results are as expected, but when trying to accessing the elements in main the results get funky.

Can anyone help me understand what I'm doing wrong please?

Below is the relevant code:

struct Matrix
{
    int rows;
    int cols;
    double **pMatrix;
};

struct Matrix unity_matrix(int row, int col);

int main()
{
    int row = 10, col = 10;
    struct Matrix m1 = unity_matrix(row, col);
    for (int x = 0; x < row; x++)
    {
        printf("\nOutside result %d\n", x);
        for (int y = 0; y < col; y++)
        {
            printf("%lf ", m1.pMatrix[x][y]);
        }
    }

    printf("\n Rows = %d Cols = %d", m1.rows, m1.cols);
    return 0;
}

struct Matrix unity_matrix(int row, int col)
{
    double v_mtrx[row][col], *p_v_mtrx[row];

    for (int x = 0; x < row; x++)
    {
        for (int y = 0; y < col; y++)
        {
            v_mtrx[x][y] = x + y + 1.0;
        }
    }

    for (int i = 0; i < row; i++) p_v_mtrx[i] = (double*) v_mtrx + i * col;

    struct Matrix mtrx = { row, col, (double **) p_v_mtrx
    };

    for (int x = 0; x < row; x++)
    {
        printf("\nInside result %d\n", x);
        for (int y = 0; y < col; y++)
        {
            printf("%lf ", mtrx.pMatrix[x][y]);
        }
    }

    return mtrx;
}
Inside result 0

1.000000 2.000000 3.000000 4.000000

Inside result 1

2.000000 3.000000 4.000000 5.000000

Inside result 2

3.000000 4.000000 5.000000 6.000000

Inside result 3

4.000000 5.000000 6.000000 7.000000

Outside result 0

1.000000 2.000000 3.000000 4.000000

Outside result 1

0.000000 -14995397491898438029096961572323840644014499700292909391043299898885044272963634128744240168119533593897190786000787827379354468352.000000 4.000000 0.000000

Outside result 2

0.000000 0.000000 0.000000 0.000000

Outside result 3

0.000000 0.000000 0.000000 0.000000
5
  • 3
    A 2d array is not the same as an array of pointers. Commented Aug 9, 2024 at 20:23
  • 4
    Block-scope variables not declared static (or extern) are automatic -- almost always "on the stack" although the standard doesn't require that -- and automatic variables aren't valid after the block exits (here after the function returns). Although using more complicated types, this is the same as stackoverflow.com/questions/1224042/… from 2009 and stackoverflow.com/questions/11656532/returning-an-array-using-c from 2012. Commented Aug 9, 2024 at 20:40
  • 1
    @dave_thompson_085 I believe he's trying to return the array itself, not a pointer to the array (which would disappear on return), as you would do for std::array in C++. In this case unity_matrix() returns a Matrix, not a * Matrix, so the return isn't a pointer context. It won't work because when setting the fields of Matrix mtrx, p_v_mtrx is a pointer to stack memory and will be freed on return. Commented Aug 9, 2024 at 21:03
  • In C it's common to pass in an array and dimensions, which will be modified. Another way is to explicitly allocate and free memory, which opens the possibility of memory leaks. Commented Aug 9, 2024 at 21:08
  • @JohnBayko yes, OP returns a struct, which is valid. But it contains pointer to a local array v_mtrx, which goes out of life. Commented Aug 9, 2024 at 21:14

3 Answers 3

2

Your approach does not work because the pointer pMatrix of the Matrix structure returned by unity_matrix points to the local array p_v_mtrx of this function. This array has automatic storage (it is a local variable) and it is discarded as soon as the function returns, so accessing it from main has undefined behavior. The pointers in this array point to the elements of the 2D array of double, itself a local object in the scope of the function, hence discarded as well when the function returns.

You must allocate the arrays from the heap and change the API so you can test for allocation error.

Here is a modified version:

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

typedef struct Matrix {
    int rows;
    int cols;
    double *values;
    double **pMatrix;
} Matrix;

bool init_unity_matrix(Matrix *m, int rows, int cols);

void free_matrix(Matrix *m) {
    free(m->values);
    m->values = NULL;
    free(m->pMatrix);
    m->pMatrix = NULL;
}

int main(void)
{
    int rows = 10, cols = 10;
    Matrix m;

    if (!init_unity_matrix(&m, rows, cols)) {
        printf("cannot allocate matrix\n");
        return 1;
    }
    printf("Unity matrix:\n");
    for (int x = 0; x < m.rows; x++) {
        for (int y = 0; y < m.cols; y++) {
            printf(" %10f", m.pMatrix[x][y]);
        }
        printf("\n");
    }
    free_matrix(&m);
    return 0;
}

bool init_unity_matrix(Matrix *m, int rows, int cols)
{
    m->rows = rows;
    m->cols = cols;
    m->values = calloc(sizeof(*m->values), (size_t)rows * cols);
    m->pMatrix = calloc(sizeof(*m->pMatrix), rows);
    if (!m->values || !m->pMatrix) {
        free_matrix(m);
        return false;
    }
    for (int y = 0; y < rows; y++) {
        m->pMatrix[y] = m->values + y * cols;
    }
    for (int y = 0; y < rows; y++) {
        for (int x = 0; x < cols; x++) {
            m->pMatrix[y][x] = y + x + 1.0;
        }
    }
    return true;
}

Here is an alternate approach where the Matrix structure is allocated as a single memory block and uses a C99 flexible array for the row pointer array pMatrix:

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

typedef struct Matrix {
    int rows;
    int cols;
    double *pMatrix[];
} Matrix;

Matrix *allocate_matrix(int rows, int cols)
{
    // compute size of structure and row array
    size_t size1 = sizeof(Matrix) + rows * sizeof(double *);
    // round up to align values array
    size_t values_offset = (size1 + sizeof(double) - 1) / sizeof(double);
    size_t total_size = sizeof(double) * (values_offset + (size_t)rows * cols);
    Matrix *m = calloc(1, total_size);
    if (m) {
        double *values = (double *)m + values_offset;
        m->rows = rows;
        m->cols = cols;
        for (int y = 0; y < rows; y++) {
            m->pMatrix[y] = values + y * cols;
        }
    }
    return m;
}

Matrix *unity_matrix(int rows, int cols)
{
    Matrix *m = allocate_matrix(rows, cols);
    if (m) {
        for (int y = 0; y < rows; y++) {
            for (int x = 0; x < cols; x++) {
                m->pMatrix[y][x] = y + x + 1.0;
            }
        }
    }
    return m;
}

void free_matrix(Matrix *m)
{
    free(m);
}

int main(void)
{
    int rows = 10, cols = 10;
    Matrix *m = unity_matrix(rows, cols);
    if (!m) {
        printf("cannot allocate matrix\n");
        return 1;
    }
    printf("Unity matrix:\n");
    for (int x = 0; x < m->rows; x++) {
        for (int y = 0; y < m->cols; y++) {
            printf(" %10f", m->pMatrix[x][y]);
        }
        printf("\n");
    }
    free_matrix(m);
    return 0;
}
Sign up to request clarification or add additional context in comments.

Comments

0

As noted in comments, a 2D array is not the same as an array of pointers. You also need to consider lifetimes. An array declared within a function has automatic lifetime, and is no longer valid after the function returns.

To accommodate this, you need to dynamically allocate the array within your struct. With checks to ensure malloc succeeded and cleanup code if it didn't.

E.g.

struct Matrix *allocMatrix(int rows, int cols) {
    struct Matrix *m = malloc(sizeof(struct Matrix));
    if (!m) return NULL;

    m->rows = rows;
    m->cols = cols;

    m->pMatrix = malloc(sizeof(double *) * rows);
    if (!m->pMatrix) {
        free(m);
        return NULL;
    }

    for (int i = 0; i < rows; i++) {
        m->pMatrix[i] = malloc(sizeof(double) * cols);
        if (!m->pMatrix[i]) {
            // Free the previously successfully allocated rows. 
            for (int j = 0; j < i; j++) {
                free(m->pMatrix[j]);
            }

            free(m);
            return NULL;
        }
    }
        
    return m;
}

By returning a pointer to the struct rather than returning the struct by value, you can:

  1. Avoid copying lots of data unnecessarily.
  2. Return NULL to indicate an allocation failure.

You will find it useful to decompose problems into highly focused functions. One function to allocate a matrix as shown above, and then another to take that properly allocated matrix and fill it out. This way you can debug the allocation separately from any concern about the data put into it.

Comments

0

To make it more clear let's at first elaborate what happens within the function unity_matrix.

In this declaration

double v_mtrx[row][col], *p_v_mtrx[row];

that is equivalent to the following declarations

double v_mtrx[row][col];
double * p_v_mtrx[row];

there are declared two arrays: the two-dimensional variable length array v_mtrx and the one-dimensional variable length array p_v_mtrx with element type double *.

After this for loop

for (int x = 0; x < row; x++)
{
    for (int y = 0; y < col; y++)
    {
        v_mtrx[x][y] = x + y + 1.0;
    }
}

the array v_mtrx looks like

1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0
2.0 3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0
3.0 4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0
4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0
5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0
and so on

In this for loop

for (int i = 0; i < row; i++) p_v_mtrx[i] = (double*) v_mtrx + i * col;

the two-dimensional array v_mtrx is reinterpreted as one-dimensional array with element type double.

Now how are elements that have the pointer type double * of the array p_v_mtrx initialized?

Due to the pointer arithmetic p_v_mtrx[0] is set to &v_mtrx[0][0], p_v_mtrx[1] is set to &v_mtrx[1][0], p_v_mtrx[2] is set to &v_mtrx[2][0] and so on.

In this declaration of an object of the structure type

struct Matrix mtrx = { row, col, (double **) p_v_mtrx
};

the initializer list is equivalent to the following

struct Matrix mtrx = { row, col, p_v_mtrx
};

because arrays used in expressions with rare exceptions are implicitly converted to pointers to their first elements.

As the element type of the array p_v_mtrx is double * then a pointer to its first element will have the type double **.

Now let's consider what happens in these nested for loops

for (int x = 0; x < row; x++)
{
    printf("\nInside result %d\n", x);
    for (int y = 0; y < col; y++)
    {
        printf("%lf ", mtrx.pMatrix[x][y]);
    }
}

The expression pMatrix[0]yields the first element of the array p_v_mtrx that in turn points to the element v_mtrx[0][0] due to its initialization by the expression &v_mtrx[0][0] as shown above. So due to the pointer arithmetic when x is equal to 0 the first row of the two-dimensional array v_mtrx that is values of expressions mtrx.pMatrix[0][y] that corresponds to values of expressions v_mtrx[0][y] are outputed. And so on as for example values of expressions mtrx.pMatrix[1][y] that corresponds to values of expressions v_mtrx[1][y] are outputed.

Now pay attention to that the arrays v_mtrx and p_v_mtrx are local array with automatic storage duration that will not be alibe after exiting the finction. So dereferencing pointers to these array after exiting the function invokes undefined behavior.

You need to allocate dynamically arrays as shown in other answers.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.