Thread: Passing and returning 2-D arrays

  1. #1
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58

    Passing and returning 2-D arrays

    Hello there,

    I'm creating a program that reads numbers from a file and then creates a 2-D matrix with them. The first line of the file has the number of rows and columns of the matrix and the next lines contains ordered pairs.

    So I wanted the user to be prompted to type the path of the file, and then pass that path to a function which would create, initialize and return the 2-D matrix. This matrix would be then passed to other functions.

    After searching a little, I found out that C has restrictions to variable sizes arrays, so here's what I am doing:

    Code:
    int * Create_Matrix (char * f_name){
        int a, b;
        
        FILE * p_file = fopen(f_name, "rt");
    
    
        int size;
        fscanf(p_file, "%d", &size);
    
    
        int matrix [size][size];
        
        Initialize_Matrix(&matrix[0][0], size);
    
    
        return &matrix[0][0];
    
    }
    The problem is that this way, in the return statement I get:
    "Address of stack memory associated with local variable 'matrix' returned."

    And if I change the matrix declaration to static int, I get:
    "Variable length array declaration can not have 'static' storage duration."

    So is there any way to make it work?

    I appreciate any advice.

  2. #2
    Registered User
    Join Date
    May 2012
    Posts
    1,066
    Quote Originally Posted by koplersky View Post
    So is there any way to make it work?
    Create the array using malloc().
    This tutorial describes several methods how you can do that.

    Bye, Andreas

  3. #3
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Your current attempt creates a local array, then fills it, then tries to return a pointer to it. It won't work, because the array, being local, vanishes when the function returns. The pointer you try to return points to somewhere in the stack, where the array used to be.

    You need to allocate the memory needed for the matrix dynamically, using malloc().

    There are ways to do it without structures, but I really recommend you dive straight into it, and use a proper structure for this, so you can support any size matrices. For example:
    Code:
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    
    struct matrix {
        int  rows;
        int  cols;
        double  *element;
    };
    
    /* Read a matrix from a file or stream,
     * into the specified matrix.
     * Returns 0 if success, errno error code otherwise.
    */
    int fread_matrix(struct matrix *const m, FILE *const input)
    {
        int  rows, cols;
        size_t  n, i;
    
        /* Invalid parameters? */
        if (!m || !input)
            return errno = EINVAL;
    
        /* Clear the matrix. */
        m->rows = 0;
        m->cols = 0;
        m->element = NULL;
    
        /* Already a read error in input? */
        if (ferror(input))
            return errno = EIO;
    
        /* Read the number of rows and columns in the matrix. */
        if (fscanf(input, " %d %d", &rows, &cols) != 2)
            return errno = ENOENT; /* Bad data in input; yields "no such file or directory" error */
    
        /* Sane matrix size? */
        if (rows < 1 || cols < 1)
            return errno = ENOENT; /* No, yields "no such file or directory" error */
    
        n = (size_t)rows * (size_t)cols;
    
        /* Allocate matrix elements. */
        m->element = malloc(n * sizeof (*m->element));
        if (!m->element)
            return errno = ENOMEM; /* Not enough memory error. */
    
        /* Read matrix elements in row-major order (C array order). */
        for (i = 0; i < n; i++)
            if (fscanf(input, " %lf", &(m->element[i])) != 1) {
                free(m->element);
                m->element = NULL;
                return errno = ENOENT; /* Invalid data, yields "no such file or directory" error. */
            }
    
        /* Done. */
        m->rows = rows;
        m->cols = cols;
    
        /* Success. */
        return 0;
    }
    
    static inline double matrix_get(const struct matrix m, const int row, const int col)
    {
        if (row >= 0 && col >= 0 && row < m.rows && col < m.cols)
            return m.element[row * m.cols + col];
        else
            return 0.0;
    }
    
    static inline double matrix_set(struct matrix m, const int row, const int col, const double value)
    {
        if (row >= 0 && col >= 0 && row < m.rows && col < m.cols)
            return m.element[row * m.cols + col] = value;
        else
            return 0.0;
    }
    
    #define ELEMENT(m, row, col) ((m).element[(col) + (row)*(m).cols])
    Because this is C, rows and columns are numbered from 0 onwards.

    Because the matrix elements are in row major order, the same order they would be in a two-dimensional C array, the element at row row, column column, is at index row*columns+column.

    The two helper functions, matrix_set() and matrix_get() verify the row and column before trying to access the element. If they are outside the matrix, both functions return zero.

    If you know you have valid row and column numbers, then you can use the ELEMENT(matrix, row, column) preprocessor macro to access an element.

    Here is an example program using the above:
    Code:
    int main(void)
    {
        struct matrix  a, b;
        int            row, col;
    
        /* Read matrix a from standard input. */
        if (fread_matrix(&a, stdin)) {
            fprintf(stderr, "Could not read matrix a from standard input.\n");
            return 1;
        } else
            fprintf(stderr, "Read matrix a: %d rows, %d columns.\n", a.rows, a.cols);
    
        /* Read matrix b from standard input. */
        if (fread_matrix(&b, stdin)) {
            fprintf(stderr, "Could not read matrix b from standard input.\n");
            return 1;
        } else
            fprintf(stderr, "Read matrix b: %d rows, %d columns.\n", b.rows, b.cols);
    
        /* Are the matrices the same size? */
        if (a.rows == b.rows && a.cols == b.cols) {
    
            fprintf(stderr, "Adding matrix b to matrix a.\n");
            for (row = 0; row < a.rows; row++)
                for (col = 0; col < a.cols; col++)
                    ELEMENT(a, row, col) += ELEMENT(b, row, col);
    
        } else
            fprintf(stderr, "Matrix b is not the same size as matrix a.\n");
    
        /* Print the matrix a. */
        fprintf(stderr, "Matrix a: %d rows, %d cols\n", a.rows, a.cols);
        fflush(stderr);
    
        for (row = 0; row < a.rows; row++) {
    
            /* All but last column: */
            for (col = 0; col < a.cols - 1; col++)
                printf("%.16g ", ELEMENT(a, row, col));
    
            /* Last column: */
            printf("%.16g\n", ELEMENT(a, row, a.cols - 1));
        }
    
        return 0;
    }


    If you intend to do more complicated stuff with your matrices, I do recommend using GNU Scientific Library.

    If you are doing this to get in-depth knowledge about matrix operations, then writing your own implementations of the matrix operations is certainly useful; in that case,
    Code:
    struct data {
        struct data *chain;
        long         references;
        size_t       size;
        double       data[];
    };
    
    struct matrix {
        long         rows;
        long         cols;
        size_t       rowstep;
        size_t       colstep;
        double      *element;
        struct data *owner;
    };
    where the matrix data is stored in a reference-counted objects maintained in a singly-linked list, will be orders of magnitude more powerful: your matrices can be dynamic, real-time views to any regular rectangular section of any other matrix. Transpose is simply swapping rows and columns; no data needs to be changed. The same for mirroring and rotation.

    The code needed to manage the reference-counted objects is pretty advanced stuff, although it makes the code that uses them much easier to write. For one, as long as you dispose of each matrix after you no longer need it, the data objects will be managed correctly (freed after they're no longer needed), without the programmer having to worry about which matrices are actually just views to other matrices, and which matrices have original data.

    So, this gets to pretty advanced level. I've just found it to be amazingly useful when developing algorithms based on matrices. (I find BLAS and LAPACK interfaces rather archaic.)

  4. #4
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58
    A thousand thanks for both of you, worked like a charm!

    Just some questions, Nominal Animal.
    Why is n of type size_t?
    Why are the functions matrix_set and matrix_get inline?
    And why are you passing to them const variables? Is this a safe measure to prevent them from being changed?

    Again, thanks for the help.

  5. #5
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by koplersky View Post
    Why is n of type size_t?
    On many architectures, such as x86-64, int is smaller (32-bit) than a pointer (64-bit). size_t is the unsigned integer type that can hold the size or length of any in-memory structure.

    If you look at functions such as malloc() or memset() or strlen(), you'll see they use the size_t type for the length.

    Quote Originally Posted by koplersky View Post
    Why are the functions matrix_set and matrix_get inline?
    They are static inline, actually. In C99 and C++ it means the compiler will include the function wherever they are called, rather than emit it once and then generate calls to them. It is basically an optimization, making the functions just as fast as if they were implemented as preprocessor macros.

    Note that in a library implementation, they would be declared and implemented in the header (.h) file. The reason they exist is that they verify the row and column, and only do the access if within the matrix.

    The ELEMENT(m, row, col) is a preprocessor macro, because it evaluates to the actual lvalue, i.e. can be used on the left side of an assignment. You can do that with an inline function, by having the function return a pointer, and always dereference the pointer, but it gets messy. I like this way better.

    Quote Originally Posted by koplersky View Post
    And why are you passing to them const variables? Is this a safe measure to prevent them from being changed?
    I do it to help the compiler generate better code.

    In simple functions it does not usually matter, because the compiler can deduce it by itself automatically, but there is never harm in it. Marking the parameters const just makes it absolutely clear to the compiler that it can cache the variables, since they are not going to change.

    For tricky functions, and especially for static inline functions, it may make a difference. If you are manipulating large matrices, you definitely want your accessor functions to be efficient.

  6. #6
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58
    Ok, thanks a lot for clarifying.

    One last thing, this call:
    fread_matrix(&b, stdin)

    When I use stdin it freezes and does nothing after I enter the file path. But it works if I declare a char pointer with the file path, add a '\n' to it by using strlen, declare a FILE pointer, pass the * char to fopen and then pass the FILE pointer to fread_matrix.
    Do you have any ideas why it won't work?

    Thanks again.

  7. #7
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by koplersky View Post
    When I use stdin it freezes and does nothing after I enter the file path.
    It expects the full matrix data (rows and columns first, then rows*columns numbers), not a file name, from standard input.

    If you want to read from a file, you do need to first fopen() the desired file (although you definitely should not add any extra characters, especially not newline, to the file name -- except perhaps make sure the string is terminated with '\0' if you construct the file name in parts), and if successful (non-NULL handle is returned), pass that to fread_matrix().

    Just like you would when using fgets(), fread(), fscanf(), and so on.

  8. #8
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58
    I'm sorry, I explained it all wrong.

    Here's what I'm doing:

    Code:
    char f_name [122];    
        fgets(f_name, sizeof(f_name), stdin);
        
        if (f_name[strlen(f_name)-1] != '\n'){
            printf("Too long!");
            printf("\n120 char limit.\n");
            exit(EXIT_FAILURE);
        }
    
    
        f_name[strlen(f_name)-1] = '\0';
        
        FILE *p = fopen(f_name, "r");
        
        if (!p) {
            exit(EXIT_FAILURE);
        }
    Which I think is similar to what you said.

    Thanks a lot for the help, everything is working now.

  9. #9
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by koplersky View Post
    Code:
        char f_name [122];    
        fgets(f_name, sizeof(f_name), stdin);
    You really should check for NULL there, or the following code may use undefined data in f_name, for example if there is no more input. Also, if the string happens to be empty (no more input), then
    Code:
        
        if (f_name[strlen(f_name)-1] != '\n'){
            printf("Too long!");
            printf("\n120 char limit.\n");
            exit(EXIT_FAILURE);
        }
        f_name[strlen(f_name)-1] = '\0';
    actually does a buffer underrun, which is a bug: you end up writing '\0' to the char before f_name. (The assignment on the last line above just removes the '\n' at the end of the string, assuming the file name was specified on a full line.)

    I'd write the above as
    Code:
        char   f_name[122];    
        size_t f_namelen;
    
        /* Read the file name from standard input */
        if (fgets(f_name, sizeof f_name, stdin) == NULL) {
            fprintf(stderr, "No file name specified in standard input.\n");
            exit(EXIT_FAILURE);
        }
    
        /* Length of the line, excluding newline (CR or LF characters) */
        f_namelen = strcspn(f_name, "\r\n");
        if (f_namelen < 1) {
            fprintf(stderr, "No file name specified in standard input.\n");
            exit(EXIT_FAILURE);
        } else
        if (f_namelen >= strlen(f_namelen)) {
            fprintf(stderr, "File name specified in standard input is too long.\n");
            exit(EXIT_FAILURE);
        } else
            f_name[f_namelen]..= '\0'; /* Remove trailing newline */
    The second if statement catches e.g. empty lines (just newline), and the last if statement lines that were too long (did not contain a trailing newline).

    (My propensity to also detect '\r' is because I've encountered the case where the data file has passed through Windows at some point. It's just more robust to treat both CR and LF as newline characters, in my opinion.)

    The way I tested my program is simple. Compile it to say ./nominal-test, and run
    Code:
    echo 2 3  0.1 0.2 0.3 0.4 0.5 0.6   2 3  9.0 8.0 7.0 6.0 5.0 4.0 | ./nominal-test
    You could also save
    Code:
    2 3
    0.1 0.2 0.3
    0.4 0.5 0.6
    
    2 3
    9.0 8.0 7.0
    6.0 5.0 4.0
    to say file matrices.txt, and run
    Code:
    ./nominal-test < matrices.txt

  10. #10
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58
    Yeah, I myself have come across the '\r' and it was making my search in a hash table fail. I didn't know about this strcspn function, thanks for the fix and all the help, I really appreciate it.

  11. #11
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    You might find the man 3 string man page useful. The bottom contains links to standard C string handling functions, as well as some extra ones, with the Conforming to section describing where the functions are available.

    The Linux man-pages in general are definitely very useful for C99 and POSIX programmers.

  12. #12
    Registered User
    Join Date
    May 2012
    Location
    Brazil
    Posts
    58
    Yes, they will certainly come in handy, thank you!

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Passing / Returning a Boolean (Help)
    By EDB in forum C++ Programming
    Replies: 5
    Last Post: 02-17-2008, 02:56 PM
  2. Passing & Returning Arrays
    By jeffdavis_99 in forum C++ Programming
    Replies: 10
    Last Post: 04-02-2005, 06:44 PM
  3. returning struct and passing as an argument
    By ronin in forum C Programming
    Replies: 4
    Last Post: 06-07-2003, 11:21 AM
  4. Passing And Returning Values
    By neo6132 in forum C Programming
    Replies: 1
    Last Post: 04-19-2003, 10:24 PM
  5. returning and passing CArrays
    By swordfish in forum Windows Programming
    Replies: 4
    Last Post: 11-01-2001, 02:59 AM