Home > Mobile >  Struggling to understand how to call a pointer to pointer in scanf
Struggling to understand how to call a pointer to pointer in scanf

Time:05-31

I'm building a program in C to run some simulations for my PhD research. Basically the program uses a function to read an input file with some important values, and then assign these values to variables in the main function. Some of these values need to be assigned to arrays, x and par, with size also declared in the input file. For that, I need to use some dynamically allocated memory inside the function, that points to the address of the pointers *x and *par.

I manage to assign the values for these pointers using 2D array notation, but I don't fully understand why it needs to be this way, as the pointers that the function are pointing to are initialized as NULL pointers.

Here is the minimum functional code as an example:

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

void read_input(char *name, int *dim, int *npar, int *np, int *ndiv, double *t, double **par, double **x);

int main(void) {
    
    int DIM;                        
    int nP;                         
    int nDiv;                       
    int nPar;                       

    // Assign values for program parameters, system parameters and initial conditions
    char *input_filename = "input.txt";
    double t;
    double *par = NULL;
    double *x = NULL;
    read_input(input_filename, &DIM, &nPar, &nP, &nDiv, &t, &par, &x);
 
    for (int i = 0; i < DIM; i  ) {
        printf("x[%i] = %.15lf\n", i, x[i]);
    }
    for (int i = 0; i < nPar; i  ) {
        printf("par[%i] = %lf\n", i, par[i]);
    }

    free(x);
    free(par);
}

void read_input(char *name, int *dim, int *npar, int *np, int *ndiv, double *t, double **par, double **x) {
    // Open input file
    FILE *input = fopen(name, "r");
    if (input == NULL) {
        printf("Input File Not Found...\n");
        return;
    }
    // Read and assign system constants
    fscanf(input, "%i", dim);
    fscanf(input, "%i", npar);
    // Read and assign program parameters
    fscanf(input, "%i %i", np, ndiv); 
    // Read and assign initial time
    fscanf(input, "%lf", t);
    // Allocate memory for x[dim] and par[npar] vectors
    *x = malloc((*dim) * sizeof(double));
    *par = malloc((*npar) * sizeof(double));
    // Security check for pointers
    if (*x == NULL || *par == NULL) {
        free(*x);
        free(*par);
        printf("Memory allocation for *x or *par did not complete successfully");
        return;
    }
    // assign IC to x[dim] vector
    for (int i = 0; i < *dim; i  ) {
        fscanf(input, "%lf ", &x[i][i]);     
    }

    for (int i = 0; i < *dim; i  ) {
        for (int j = 0; j < *dim; j  ) {
            printf("x[%i][%i] = %lf (address: %p)\n", i, j, x[i][j], &x[i][j]);
        }
    }
    printf("==============================\n");
    // Assign values to par[npar] vector
    for (int i = 0; i < *npar; i  ) {
            fscanf(input, "%lf\n", &par[i][i]);
    }
    for (int i = 0; i < *npar; i  ) {
        for (int j = 0; j < *npar; j  ) {
            printf("par[%i][%i] = %lf (address: %p)\n", i, j, par[i][j], &par[i][j]);
        }
    }
    printf("==============================\n");
        
    // Close input file
    fclose(input);
}

Also, I checked the addresses that the double pointers x[i][j] and par[i][j] are pointing to, and they are the same for each combination of [i][0], ..., [i][4], with i = 0 to 4:

x[0][0] = 0.707107 (address: 0000020B01C33FC0)
x[0][1] = 0.000000 (address: 0000020B01C33FC8)
x[1][0] = 0.707107 (address: 0000020B01C33FC0)
x[1][1] = 0.000000 (address: 0000020B01C33FC8)
==============================
par[0][0] = 1.000000 (address: 0000020B01C34580)
par[0][1] = 0.150000 (address: 0000020B01C34588)
par[0][2] = 0.025000 (address: 0000020B01C34590)
par[0][3] = -0.500000 (address: 0000020B01C34598)
par[0][4] = 1.000000 (address: 0000020B01C345A0)
par[1][0] = 1.000000 (address: 0000020B01C34580)
par[1][1] = 0.150000 (address: 0000020B01C34588)
par[1][2] = 0.025000 (address: 0000020B01C34590)
par[1][3] = -0.500000 (address: 0000020B01C34598)
par[1][4] = 1.000000 (address: 0000020B01C345A0)
par[2][0] = 1.000000 (address: 0000020B01C34580)
par[2][1] = 0.150000 (address: 0000020B01C34588)
par[2][2] = 0.025000 (address: 0000020B01C34590)
par[2][3] = -0.500000 (address: 0000020B01C34598)
par[2][4] = 1.000000 (address: 0000020B01C345A0)
par[3][0] = 1.000000 (address: 0000020B01C34580)
par[3][1] = 0.150000 (address: 0000020B01C34588)
par[3][2] = 0.025000 (address: 0000020B01C34590)
par[3][3] = -0.500000 (address: 0000020B01C34598)
par[3][4] = 1.000000 (address: 0000020B01C345A0)
par[4][0] = 1.000000 (address: 0000020B01C34580)
par[4][1] = 0.150000 (address: 0000020B01C34588)
par[4][2] = 0.025000 (address: 0000020B01C34590)
par[4][3] = -0.500000 (address: 0000020B01C34598)
par[4][4] = 1.000000 (address: 0000020B01C345A0)
==============================
x[0] = 0.707106781186547
x[1] = 0.000000000000000
par[0] = 1.000000
par[1] = 0.150000
par[2] = 0.025000
par[3] = -0.500000
par[4] = 1.000000

Here is the format of the input.txt file:

2
5

1000 1000

0.0
0.707106781186547 0.0

1.0
0.15
0.025
-0.5
1.0

What am I missing here?

CodePudding user response:

I manage to find in some articles the correct way to assign values to the pointers:

For that, the pointer should be accessed via 1D array notation by calling the pointer itself as:

scanf("%d", &(*x)[i]);

instead of

scanf("%d", &x[i][i]); 

Then, the values can be assigned correctly.

CodePudding user response:

The code posted has undefined behavior in all cases where the first index in the 2D indexing is non 0. For example:

    fscanf(input, "%lf ", &x[i][i]); 
  • x is the address of a pointer to int in the main function.
  • x[i] is only defined if i is 0, otherwise you are reading from a location in memory beyond that of the main local variable, which has undefined behavior, but may or may not cause a problem.
  • x[i][i] dereferences the value x[i], which is probably an invalid pointer, hence would highly likely cause a crash. Merely taking the address of this as &x[i][i] might still go unnoticed, but when fscanf() tries to store an int there, dereferencing &x[i][i] will be a problem.

It is very surprising that your program produces output without a problem.

The correct syntax for this loop is

// assign IC to x[dim] vector
for (int i = 0; i < *dim; i  ) {
    fscanf(input, "%lf ", &(*x)[i]);
}

&(*x)[i] is the address of the i-th element of the array pointed to by *x. There are other possibilities, none of which are satisfying:

  • &x[0][i]
  • (*x) i
  • *x i
  • x[0] i
  • i *x
  • &0[x][i]
  • &i[*x]
  • &i[0[x]]
  • i 0[x]

Yes, all of the above are equivalent...

A better approach is to define local variables with the correct type in read_input and transmit them back to the caller after successful operation. You could also group all these variables in a structure and pass a pointer to read_input:

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

int read_input(const char *name, int *pdim, int *pnpar,
               int *pnp, int *pndiv, double *pt,
               double **ppar, double **px)
{
    // use regular variables
    int dim, npar, np, ndiv;
    double t;
    double *par = NULL;
    double *px = NULL;

    // Open input file
    FILE *input = fopen(name, "r");
    if (input == NULL) {
        printf("Cannot open file %s: %s\n", name, strerror(errno));
        return -1;
    }
    // Read and assign system constants
    if (fscanf(input, "%i %i", &dim, &npar) != 2)
        goto invalid;
    // Read and assign program parameters
    if (fscanf(input, "%i %i", &np, &ndiv) != 2)
        goto invalid;
    // Read and assign initial time
    if (fscanf(input, "%lf", &t) != 1)
        goto invalid;
    // Allocate memory for x[dim] and par[npar] vectors
    x = calloc(dim, sizeof(double));
    par = calloc(npar, sizeof(double));
    // Security check for pointers
    if (x == NULL || par == NULL) {
        printf("Memory allocation for x or par did not complete successfully\n");
        free(x);
        free(par);
        fclose(input);
        return -1;
    }
    // assign IC to x[dim] vector
    for (int i = 0; i < dim; i  ) {
        if (fscanf(input, "%lf", &x[i]) != 1)
           goto invalid;
    }

    // Assign values to par[npar] vector
    for (int i = 0; i < npar; i  ) {
        if (fscanf(input, "%lf", &par[i]) != 1)
            goto invalid;
    }
        
    // Close input file
    fclose(input);

    // update valid data to the caller
    *pdim = dim;
    *pnpar = npar;
    *pnp = np;
    *pndiv = ndiv;
    *pt = t;
    *px = x;
    *ppar = par;
    return 0;

invalid:
    printf("Invalid or missing input\n");
    free(x);
    free(par);
    fclose(input);
    return -1;
}
  • Related