How to turn a 2D Array into 2D Pointer in C

Asked

Viewed 40 times

3

It is possible for a function to receive as a parameter a reference to the address of a two-dimensional array (matrix) static, and from the pointer to access the elements of this matrix.

In the example below when I try to calculate the determinant of the matrix A, the function findDet(double **arr1), cannot iterate, and the code generates the Segmentation Fault error:

#include <stdio.h>

double findDet(double **arr1){
    int det = 0;
    for(int i=0;i<3;i++){
      det = det + (arr1[0][i]*(arr1[1][(i+1)%3]*arr1[2][(i+2)%3] - arr1[1][(i+2)%3]*arr1[2][(i+1)%3]));
    }
    return det;
}

int main()
{
    double determinante;
    double A[3][3] = {{5, 2, 4}, {1, 2, 3}, {2, 2, 5}};
    determinante = findDet(A);

    printf("%f", determinante);
    printf("Programa finalizado");
    return 0;
}

I know I can fix by changing the way I receive the parameter in the function findDet() :

#include <stdio.h>

double findDet(double arr1[3][3]){
    int det = 0;
    for(int i=0;i<3;i++){
      det = det + (arr1[0][i]*(arr1[1][(i+1)%3]*arr1[2][(i+2)%3] - arr1[1][(i+2)%3]*arr1[2][(i+1)%3]));
    }
    return det;
}

int main()
{
    double determinante;
    double A[3][3] = {{5, 2, 4}, {1, 2, 3}, {2, 2, 5}};
    determinante = findDet(A);

    printf("%f", determinante);
    printf("Programa finalizado");
    return 0;
}

I think it happens dirty of the different types.

I didn’t want to have to change the header of the function findDet(), it is possible to change my type variable double A[3][3] in double **A, before passing as parameter to my function?

If I can do this transformation, my function should run smoothly, right?

3 answers

1

In C an array is read as a vector, and when you try to use a double pointer, it ends up searching for a memory address that is not allocated to its vector.

int matriz[2][2] = {{1,4},{3,7}}
/* é o mesmo que [1,4,3,7] */

To fix your access you need to work with only one position on your pointer.

int *ponteiro = matriz;
printf("%d\n", ponteiro[3]);
// escreve 7

1


The key detail is that double A[3][3] is not an array of arrays internally, but a continuous zone of memory accessed in a special way. For this reason when you will try to access within the function imagining that you have a double** as a pointer to a pointer the code fails.

See my answer in the question Matrix with out-of-range index returning correct values to better understand the problem.

There are two solutions to this problem:

Change function parameter

This is the solution you proposed, and in my opinion, it’s the simplest and best for the code you have. If the problem is to force the dimension into the function, you can avoid it by making the definition of the size dynamic, thus:

double findDet(int linhas, int colunas, double arr1[linhas][colunas]){

Then in the function call you can define the number of rows and columns the array has:

determinante = findDet(3, 3, A);

This allows the function to be used in arrays with different dimensions without having to change it.

Watch it work on Ideone

Note that the two extra row and column parameters are required for the compiler to be able to perform the internal array access calculations.

Change the array declaration

Exchanging the array declaration and initialization for a dynamic allocation with malloc can effectively create an array of arrays for the function to function as it is, with the double **arr1.

This naturally makes the creation code of it much more extensive and it is necessary to delete memory when no longer useful, using the function free.

It would be done so:

double **A = malloc(sizeof(double*) * 3);
A[0] = malloc(sizeof(double) * 3);
A[1] = malloc(sizeof(double) * 3);
A[2] = malloc(sizeof(double) * 3);
A[0][0] = 5;
A[0][1] = 2;
A[0][2] = 4;
A[1][0] = 1;
A[1][1] = 2;
A[1][2] = 3;
A[2][0] = 2;
A[2][1] = 2;
A[2][2] = 5;

Watch it work on Ideone

You can change the declaration of each line to be made through a for but it will not save much code. If the values are read from the console or file, this code is greatly reduced getting only the allocations.

  • Then I’ll have to resort to dynamic memory allocation to change the array declaration! Thanks.

0

I didn’t want to have to change the header of the function findDet(), it is possible to transform my variable of type double A[3][3] in double **A, before passing as parameter to my function?

Your type variable double A[3][3] is the kind you declared, double[3][3], what your compiler can tell you. It’s a pointer and occupies what occupies a double* on your platform. Say 8 bytes for an x64 build.

Your type variable double **A would be best represented as double** A, and is like double**. It is a pointer and occupies what occupies a POINTER to double*, the same 8 bytes in x64.

If you’re starting to program in this language, maybe you should declare things as they are. This is because it facilitates understanding and visualization and also helps to interpret compiler messages.

When you declare double** A is declaring A. A name. And declaring A as a variable of a type, double**. If A is double** then *A is double*, and **A is double. This is the result of the operator * in C, the one dereference Operator or indirect Operator. This is a consequence of the statement and not the statement.

If I can do this transformation, my function should run smoothly, right?

No. What is missing in your case is the size. You need to pass the size. C doesn’t even have matrices like FORTRAN. There are only scalar and vector types. And vectors are represented by base + displacement. Their model lacks displacement. For "matrices" there are vector vectors and vector vectors of vectors and so on.

An example of such pointers and the transformation

    double A[3][3];
    // agora uma "hierarquia"
    double   um_double = 42.;
    double*  pD   = &um_double;
    double** B = &pD;

This is a minimal representation of things as stated, A and B. To make it clear what is what it is like to declare and use pointers for these things:

    // um ponteiro para A
    double      (*pA)[3][3] = &A;
    // um ponteiro para B
    double***   pB = &B;

And using it to access A[0][0] or um_double:

    A[0][0] = 42.42; // um exemplo
    A[2][2] = 84.84;
    // usando os ponteiros para acessar os dados
    printf("A[0][0] = %f (usando o ponteiro\n", (*pA)[0][0]);
    printf("Usando B para acessar o valor = %f\n", ***pB);

that shows

A[0][0] = 42.420000 (usando o ponteiro
Usando B para acessar o valor = 42.000000

and the transformation

    pD = (double*)A;
    printf("Usando B para acessar o inicio de A[][] = %f\n", ***pB);

showcase

Usando B para acessar o inicio de A[][] = 42.420000

And it’s clear that pB points to the beginning of A. Only the index information is gone.

In addition to the base you need to have the offset. So you can only access the first --- without doing the math. And using the indexes will not be accepted by the compiler because it does not have this information.

Recovering the displacement

    pA = (double(*)[3][3])pD;
    printf("Usando B para acessar A[2][2] = %f\n", (*pA)[2][2]);

That’s the transformation that you spoke of. And it shows

Usando B para acessar A[2][2] = 84.840000

The expected value, after a cast to the correct format. But it only works if you know the size and so on.

The whole program

#include <stdio.h>

int main(void)
{
    double A[3][3];
    // agora uma "hierarquia"
    double   um_double = 42.;
    double*  pD   = &um_double;
    double** B = &pD;

    // um ponteiro para A
    double      (*pA)[3][3] = &A;
    // um ponteiro para B
    double***   pB = &B;

    A[0][0] = 42.42; // um exemplo
    A[2][2] = 84.84;
    // usando os ponteiros para acessar os dados
    printf("A[0][0] = %f (usando o ponteiro\n", (*pA)[0][0]);
    printf("Usando B para acessar o valor = %f\n", ***pB);

    pD = (double*)A;
    printf("Usando B para acessar o inicio de A[][] = %f\n", ***pB);

    pA = (double(*)[3][3])pD;
    printf("Usando B para acessar A[2][2] = %f\n", (*pA)[2][2]);

    return 0;
};

The way out

A[0][0] = 42.420000 (usando o ponteiro
Usando B para acessar o valor = 42.000000
Usando B para acessar o inicio de A[][] = 42.420000
Usando B para acessar A[2][2] = 84.840000
  • On the issue of me not passing the size, in this program only have square matrices 3x3, but thank you for warning. I’ve known C for a few years, but I’ve always been confused with pointer questions and the use of operators * and &. From what I had understood of your answer if I declared my static matrix in this way double A[3][3], initialize the elements, and then create a pointer by doing double (*pA)[3][3] = &A, I would be creating pointers that point to my Matrix, but I wouldn’t be able to go through that structure because of the way it was created?

  • The program that I showed covers the common cases, round and round I think. As you said now I could yes go through the matrix by the indexes, but pA has already embedded the size of the matrix and so I said it would be little useful. A common solution would be to encapsulate the matrix in a typedef along with the dimensions. If they are all 3x3 and do not need anything generic is fine as I showed at the end of the example

Browser other questions tagged

You are not signed in. Login or sign up in order to post.