0

I have a matrix in a file like:

3
1 2 3
4 5 6
7 8 -9

where the first line indicates the square matrix order. I'm using the following code to read the file and store it into a vector (I have removed all if checks for sake of simplicity):

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

int read_matrix_file(const char *fname, double *vector)
{
    /* Try to open file */
    FILE *fd = fopen(fname, "r");
    char line[BUFSIZ];
    fgets(line, sizeof line, fd);

    int n;
    sscanf(line, "%d", &n)

    vector = realloc(vector, n * n * sizeof *vector);
    memset(vector, 0, n * n * sizeof *vector);

    /* Reads the elements */
    int b;
    for(int i=0; i < n; i++) {
        // Read the i-th line into line
        if (fgets(line, sizeof line, fd) == NULL) {
            perror("fgets");
            return(-1);
        }

        /* Reads th j-th element of i-th line into the vector */
        char *elem_ptr = line;
        for (int j=0; j < n; j++) {
            if(sscanf(elem_ptr, "%lf%n", &vector[n*i+j] , &b) != 1) {
                perror("sscanf");
                return(1);
            }
            elem_ptr += b;
        }
    }
    fclose(fd);

    /* HERE PRINTS OK */
    for(int i=0; i<n*n; i++)
        printf("%i %f\n",i, vector[i]);

    return n;
}

The read_matrix_file receives a filename and an array of doubles and fill the array, returning the matrix order. The expected usage can be seen in this code block.

int main(void)
{

    const char *fname = "matrix.txt";
    double *vector = malloc(sizeof * vector);

    int n = read_matrix_file(fname, vector);

    /* Here prints junk */
    for(int i=0; i<n*n; i++)
        printf("%i %f\n",i, vector[i]);

    free(vector);
}

The issue is, the printf works fine inside the read_matrix_file but seems invalid in main.

I'm allocating the array outside the function and passing it by "reference", but I'm very suspecious of realloc, unfortunatelly I don't know how to fix or a better approach.

Lin
  • 1,145
  • 11
  • 28
  • 3
    Google *Updating pointers in a function in C* - you need to pass vector by `**`. – rafix07 Sep 04 '19 at 07:36
  • 4
    Remember that in C all arguments are passed *by value*. That means their values are *copied* into the functions local argument variables. Modifying a copy (like assigning to it) will not modify the original. Please do some research about *emulating pass by reference in C*. – Some programmer dude Sep 04 '19 at 07:37
  • 3
    Also note that you should never assign back to the pointer you pass to [`realloc`](https://en.cppreference.com/w/c/memory/realloc). If `realloc` fails and return a null pointer, then you lose the original pointer and have a memory leak. – Some programmer dude Sep 04 '19 at 07:39
  • 3
    Possible duplicate of [Updating pointers in a function](https://stackoverflow.com/questions/29658614/updating-pointers-in-a-function) – Gerhardh Sep 04 '19 at 07:42
  • 1
    What is `start`? Is it supposed to be `elem_ptr `? – Weather Vane Sep 04 '19 at 07:44
  • @WeatherVane indeed. fixed. – Lin Sep 04 '19 at 07:49
  • @Gerhardh, I don't think is exactly equal, since the `realloc` here is absent in the duplicate candidate. – Lin Sep 04 '19 at 07:56
  • It does not matter if the pointer is modified via `++` or via a call to `realloc`. It is exactly the same mechanism. – Gerhardh Sep 04 '19 at 07:58
  • @Gerhardh can you please point me some document to learn more about the internal `realloc` mechanism? – Lin Sep 04 '19 at 08:10
  • Which internal `realloc` mechanism? It is about modifying a variable in a function. It does not matter if you do this with `malloc`, `realloc`, `++`, `=&other_variable` or any other machanism changing the value of a variable. It is only about adding another level of indirection to the variable. If you do not have the address of the variable outside your function you cannot change it. You can only change a copy of the value which will be thrown away after you return. – Gerhardh Sep 04 '19 at 08:17
  • regarding: `vector = realloc(vector, n * n * sizeof *vector);` and `int read_matrix_file(const char *fname, double *vector)` The call to `realloc()` can/will change where the callers' `vector` is pointing. To successfully do that, must pass the address of `vector` so the signature should be: `int read_matrix_file(const char *fname, double **vector)` Then, all references to `vector` need to be modified (except the call to `realloc()` ) to properly de-reference that parameter – user3629249 Sep 05 '19 at 07:47

1 Answers1

3

You are reallocating memory inside read_matrix_file() and storing the elements of the matrix in that memory region. But when you come out of the function, since the pointer vectoris a local variable, its new value is lost when you leave the function.

When you come back inside main() vector still points to the (now likely invalid) memory region that you had previously allocated with malloc().

You should either allocate large enough memory before calling read_matrix_file or pass a double pointer (**) if you want to modify the pointer and see the change reflected back in main()

What I meant is something like this:

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

int read_matrix_file(const char *fname, double **p_vector)
{
    /* Try to open file */
    FILE *fd = fopen(fname, "r");
    char line[BUFSIZ];
    fgets(line, sizeof line, fd);

    int n;
    sscanf(line, "%d", &n);

    *p_vector = realloc(*p_vector, n * n * sizeof **p_vector);
    double *vector = *p_vector;

    memset(vector, 0, n * n * sizeof *vector);

    /* Reads the elements */
    int b;
    for(int i=0; i < n; i++) {
        // Read the i-th line into line
        if (fgets(line, sizeof line, fd) == NULL) {
            perror("fgets");
            return(-1);
        }

        /* Reads th j-th element of i-th line into the vector */
        char *elem_ptr = line;
        for (int j=0; j < n; j++) {
            if(sscanf(elem_ptr, "%lf%n", &vector[n*i+j] , &b) != 1) {
                perror("sscanf");
                return(1);
            }
            elem_ptr += b;
        }
    }
    fclose(fd);

    /* HERE PRINTS OK */
    for(int i=0; i<n*n; i++)
        printf("%i %f\n",i, vector[i]);

    return n;
}

In main, call it with:

int n = read_matrix_file(fname, &vector);

EDIT: Please note that this code does not handle the failure of realloc() properly.

th33lf
  • 2,177
  • 11
  • 15
  • passing a `**` how can I handle the realloc? The problem is, I don't know how large my matrix will be before read the file. Any other better strategies? – Lin Sep 04 '19 at 07:54
  • 1
    @Lin You handle the size in the same way as before. Instead of `vector` you need to use `*vector` – Gerhardh Sep 04 '19 at 08:01
  • I think you missed `*p_vector` in `realloc` first parameter – Lin Sep 04 '19 at 08:06
  • Just curious, why are you allocating memory with `malloc` in the main function and then `realloc` inside the function? Why not just `malloc` inside `read_matrix_file`? – alrevuelta Sep 04 '19 at 16:57
  • Why not just use 'malloc()' in the called function? Because the (already set values in`vector`) need to be kept and `realloc()` will keep those values – user3629249 Sep 05 '19 at 07:53
  • @user3629249 In this specific case, the OP doesn't seem to care about any of the data that was in the buffer before the `realloc`. I mean he doesn't even initialize it after the `malloc`. – th33lf Sep 05 '19 at 08:06
  • indeed. I don't care (and I don't know) anything about the data structure before I open the file and read the first line (that I do inside the `read_matrix_file`), I just `malloc` beforehand to keep the pointer address in call block. Any other ways to accomplish that? – Lin Sep 06 '19 at 05:35
  • @Lin In that case, you could simply pass in a (pointer-to-) NULL pointer and do a `malloc()` within `read_matrix_file` instead of `realloc()`, as suggested by @alrevuelta – th33lf Sep 06 '19 at 07:48