Search code examples
carraysmultidimensional-arrayreturn

How do I return a 2D array inside a function to main in C


I read a text file and store the contents in a 2D array called H inside a function, I need to return this 2D array to main somehow so I can use it there and pass it to other functions. I'm not sure how to get the return to work, maybe using pointers. I made the readTxtFile function into a void function to test if the file reading was working (it does) but I can't do anything with the 2D array outside the function. I have two functions getRows() and getCols() that I didn't show here, I can if needed though.

Heres my code so far:

int main(void){

    //  int *H;
    //  H = readTxtFile("H.txt");
    readTxtFile("H.txt");
    return 0;
}

void readTxtFile(char *filename){
    int rows, cols;
    FILE *fp = fopen(filename, "r");
    if (!fp){
        perror("can't open H.txt\n");
        //return EXIT_FAILURE;
    }
    rows = getRows(fp);
    cols = getCols(fp);
    int (*H)[cols] = malloc(sizeof(int[rows][cols]));
    if(!H){
        perror("fail malloc\n");
        exit(EXIT_FAILURE);
    }

    for(int r = 0; r < rows; ++r){
        for(int c = 0; c < cols; ++c){
            if(EOF==fscanf(fp, "%d", &H[r][c])){
                fprintf(stderr, "The data is insufficient.\n");
                free(H);
                exit(EXIT_FAILURE);
            }
        }
    }
    fclose(fp);

    // printH(rows,cols,H);
    //  return H;


}

This is what the text file looks like:

1 1 0 1 0 0
0 1 1 0 1 0
1 0 0 0 1 1
0 0 1 1 0 1
2 2 2 2 2 2

Any help would be appreciated


Solution

  • What I would do is defining a structure for the 2D array in term of its:

    • number of columns
    • number of rows
    • pointer to array data in memory

    Note that I would "linearize" the array, i.e. allocate a single block of memory of size Columns * Rows * sizeof(int), and given the i and j row and column index, these two indexes can be converted with simple math to a single index in the 1D array (e.g. index = rowIndex * Columns + columnIndex)

    Then, I would just return a pointer to this structure from your ReadTxtFile function:

    struct IntArray2D {
        int Rows;
        int Columns;
        int* Elements;
    };
    
    /* 
     * Define a couple of helper functions to allocate 
     * and free the IntArray2D structure. 
     */
    struct IntArray2D* IntArray2D_Create(int rows, int columns);
    void IntArray2D_Free(struct IntArray2D* array);
    
    /* 
     * On success, returns a 2D array with data read from file.
     * On failure, returns NULL.
     * NOTE: callers must call IntArray2D_Free() when done 
     */
    struct IntArray2D* ReadTxtFile(const char* filename);
    

    EDIT As an alternative, you could define the array structure has having a header block with rows and columns count, immediately followed by the "linearized" 2D array elements, using a "flexible array member":

    struct IntArray2D {
        int Rows;
        int Columns;
        int Elements[];
    };
    

    You can then define some convenient functions to operate on this custom array structure, e.g.:

    struct IntArray2D* IntArray2D_Create(int rows, int columns)
    {
        /* Check rows and columns parameters are > 0 */
        /* ... */
    
        struct IntArray2D *p = malloc(sizeof(struct IntArray2D)
                                      + rows * columns * sizeof(int));
        if (p == NULL) {
            return NULL;
        }
    
        p->Rows = rows;
        p->Columns = columns;
    
        /* May zero out the array elements or not... */
        memset(p->Elements, 0, rows * columns * sizeof(int));
    
        return p; 
    }
    
    void IntArray2D_Free(struct IntArray2D* array)
    {
        free(array);
    }
    
    int IntArray2D_GetElement(struct IntArray2D* array, 
                                     int row, int column)
    {
        /* Check array is not NULL; check row and column 
           indexes are in valid ranges ... */
    
        int index = row * (array->Columns) + column;
        return array->Elements[index];
    }
    
    void IntArray2D_SetElement(struct IntArray2D* array, 
                                      int row, int column,
                                      int value)
    {
        /* Check array is not NULL; check row and column 
           indexes are in valid ranges ... */
    
        int index = row * (array->Columns) + column;
        array->Elements[index] = value;
    }
    

    Inside your ReadTxtFile function, instead of calling malloc, you can call IntArray2D_Create:

    struct IntArray2D* ReadTxtFile(const char* filename) 
    { 
        struct IntArray2D* data = NULL;
    
        /* ... */
    
        rows = getRows(fp);
        cols = getCols(fp);
        data = IntArray2D_Create(rows, cols);
        if (data == NULL) {
            /* Handle error ... */ 
            return NULL;
        }
    
        /* Fill the array ... */
    

    In particular, instead your:

    if(EOF==fscanf(fp, "%d", &H[r][c])){
    

    you can do:

        /* int valueReadFromFile */
        if (EOF == fscanf(fp, "%d", &valueReadFromFile)) {
            fprintf(stderr, "The data is insufficient.\n");
        }      
        IntArray2D_SetElement(data, r, c, valueReadFromFile);
    

    And then at the end of the function, you can just have:

        return data;
    }