user3716193 user3716193 - 1 month ago 8
C Question

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

Answer

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);
}

/* Can be declared inline to reduce overhead */
int IntArray2D_GetElement(struct IntArray2D* array, 
                                 int row, int column)
{
    int index = row * (array->Columns) + column;
    return array->Elements[index];
}

/* Can be declared inline to reduce overhead */
void IntArray2D_SetElement(struct IntArray2D* array, 
                                  int row, int column,
                                  int value)
{
    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 ... */ 
    }

    /* 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;
}