2

I have looked through similarly worded questions and had no luck with my problem. I am aiming to read in a matrix from a text file in C, and then perform operations on it. Currently, I am writing a function to read in the matrix. The text files always have the format

# Comment
# Comment
matrix NROWS NCOLS
DATA DATA DATA
DATA DATA DATA etc
end

so for a 3x4 matrix:

# Comment
# Comment
matrix 3 4
7.16669652013   8.21223145665   8.78374366033   5.87828106521   
7.32124040244   4.30552353817   1.67905652974   3.91825198378   
7.28717047595   3.83999063812   5.53693148121   6.03363152874
end

My function currently is:

void read_matrix(char *filename, int rows, int cols) {
    char *status;
    char line[MAX_FILE_LINE_SIZE];
    int line_no = 1;
    char *data_line;
    double matrix[rows][cols];

    printf("Reading Matrix...\n");
    FILE *inf = fopen(filename, "r");
    if (!inf) {
        fprintf(stderr, "Error, could not open file '%s'\n", filename);
        exit(1);
    }
    
    status = fgets(line, sizeof(line), inf);
    while (status) {
        status = fgets(line, sizeof(line), inf);
        ///printf("%i: %s", line_no, line);
        if (line_no >= 3 && line_no < 3+rows) {
            int i=0;
            data_line = strtok(line, " \t");
            while (data_line != NULL) {
                matrix[line_no-3][i] = atof(data_line);
                data_line = strtok(NULL, " \t");
                i++;
            }
        }
        line_no ++;
    }
    printf("PRINT MATRIX\n");
    for (int z=0; z<rows; z++) {
        for (int j=0; j<cols; j++) {
            printf("matrix[%i][%i] = %f\n",z, j, matrix[z][j]);
        }
    }
    fclose(inf);
}

which works great, and so at the end of the function I have all the data saved into a 2D array matrix[rows][cols]. However I am completely stumped on how to then use this function in main, as outside of the function the variable is no longer in scope. Any help would be very appreciated.

3 Answers3

1

VLA (variable length arrays) are not advised in C. Add an "allocate_matrix" function, which would return a double ** matrix allocated with malloc, and return it to main ! Dont forget to free everything after use ;)

  • 1
    You can't pass a true two-dimensional array such as `double matrix[rows][cols];` as a double pointer. – Andrew Henle Oct 14 '20 at 12:39
  • VLAs were created for use in `C` in `C99`, and are still included in the most recent standards, they are a very useful method, and their usage is not discouraged in `C` programming – ryyker Oct 14 '20 at 13:13
  • In C11 they are optional, and they can easily be misused. But depends on your intention, they can be great additions yes – MagicWarthog Oct 15 '20 at 14:15
1

"However I am completely stumped on how to then use this function in main, as outside of the function the variable is no longer in scope"

You have correctly identified the problem as scope. It can be addressed in several ways. I like the following:

    double ** read_matrix(char *filename, int *rows, int *cols);//requires memory allocation

In this, the function will return array, and filename is passed as a parameter, and because you do not know the size parameters until the file is read, pass pointers to rows and cols. It would be called as:

int main(void)
{
     const char filename[] = {"c:\\somefile.txt"};
     double **array = NULL;
     int rows, cols;
     ... other variables
     array = read_matrix(filename, &rows, &cols);
     if(array)
     {
          // use array
          ...
          free(array);
     }

The internals of read_matrix() include:

  • reading and parsing the file to get values for rows, cols and data for array.
  • using rows, cols, allocate memory to double **array. (malloc() used)
  • populate new dynamically allocated array with data values.
  • set *cols == to internally determined value for columns.
  • set *rows == to internally determined value for rows.
  • return array.

And, back in main(),

  • use the array object.
  • free(array);

Inside the read_matrix() function, the memory allocation that is called could (and probably should) be implemented as another function, such as:

double ** Create2D(int c, int r)
{   
    double **arr;
    int    y;
    
    arr   = calloc(c, sizeof(double *));
    for(y=0;y<c;y++)
    {
        arr[y] = calloc(r, sizeof(double)); 
    }
    return arr;
}

Called as:

double ** read_matrix(char *filename, int *rows, int *cols)
{
     int cols=0, rows=0, i, j;
     
     //variables, initializations
     //read and parse files
     //convert values read from file from text into `cols` and `rows`
     //allocate memory for double **array
     //place data values from file into array
     double **array = Create2D(rows, cols
     if(array)
     {
         for(i=0;i<rows;i++)
         {
             for(j=0;j<cols;j++)
             {
                  array[i][j] = data[i][j];
             }
         }
     }
     return array;  //must test that array is not null in caller
 }

Where could Create2D() be implemented as:

double ** Create2D(int r, int c)
{   
    double **arr;
    int    y;
    
    arr   = calloc(c, sizeof(double *));
    for(y=0;y<c;y++)
    {
        arr[y] = calloc(r, sizeof(double)); 
    }
    return arr;
}                 
ryyker
  • 22,849
  • 3
  • 43
  • 87
  • I'm getting an error from the `Create2D()` function, the line `arr[y] = calloc(r, sizeof(double));` is giving me the error: "incompatible types when assigning to type 'double' from type 'void *'". EDIT: This was just a typo in my code, I had initialised `arr` as `double *arr;` instead of `double **arr;`. – AceTorterra1 Oct 14 '20 at 14:39
  • @AceTorterra1 - Are you compiling in a `C++` compiler? If so, it is required that you cast the return of `[m][c]alloc()`, i.e., `arr[y] = (double)calloc(r, sizeof(double));`, otherwise, if `C` compiler casting [is not recommended](https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc). – ryyker Oct 14 '20 at 14:44
  • 1
    This has worked perfectly, thank you! Out of interest, why is it that you advise the memory allocation to take place in a separate function? – AceTorterra1 Oct 14 '20 at 15:09
  • @AceTorterra1 - You are welcome. _Why[?]_ Its just my preference. But in general I believe many programmers would agree its a good idea to keep the primary purpose of a function as pure as possible, by keeping it brief, and uncluttered by putting code for _support functionality_, (eg allocating memory for a multi-dimensional array) somewhere else. Benefits of moving out to a separate function readability and maintainability. – ryyker Oct 14 '20 at 16:43
0

you will need to dynamically allocate your matrix with malloc and return it from your function, that way you will be able return it from the function and access the information you saved outside of the scope, there is no way to do this in C without dynamically allocating it. here's an answer from another user to show how to dynamically allocate a matrix: https://stackoverflow.com/a/2128754/6365408 the intuitive way is to allocate a pointer pointer to a double the size of columns, and in each index allocate a row in a for loop:

int **mat = (int **)malloc(rows * sizeof(int*));
for(int i = 0; i < rows; i++) mat[i] = (int *)malloc(cols * sizeof(int));

but you lose on locality of reference which will affect performance. the 2nd option is:

int *mat = (int *)malloc(rows * cols * sizeof(int));

Then, you simulate the matrix using

int offset = i * cols + j;
// now mat[offset] corresponds to m(i, j)

for row-major ordering and

int offset = i + rows * j;
// not mat[offset] corresponds to m(i, j)

in the end you will have to free all the allocations you've made with malloc.

Gilad
  • 305
  • 1
  • 2
  • 8