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 toint
in themain
function.x[i]
is only defined ifi
is0
, otherwise you are reading from a location in memory beyond that of themain
local variable, which has undefined behavior, but may or may not cause a problem.x[i][i]
dereferences the valuex[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 whenfscanf()
tries to store anint
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;
}