Reputation: 11
Trying to understand pointers, basically 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 point 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
Upvotes: 0
Views: 117
Reputation: 144989
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;
}
Upvotes: 1
Reputation: 311088
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.
Upvotes: 0
Reputation: 36621
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:
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.
Upvotes: 0