Your thought of using fgets to perform line-oriented input on the contents of the file is a good one. In all but rare circumstances, it is much better to approach your read of either file-input or user-input in that way. You can then parse the information you need from the buffer filled in one or a number of ways.
One of your biggest problems here is:
int readFile(char* fileName, Max max)
where you are passing the struct max by-value. What this means is readFile receives a copy of the struct and any changes you make to the struct are lost on function return. Why? Max is type struct maximum, it is not an array so it is not converted to a pointer on access. Instead, you need to pass the address of max in order to operate on that memory address within the function so that the changes made are available back in the calling function.
(It is also curious that you chose to create a single-member struct holding a 2D array instead of simply using the 2D array itself. While perfectly legal -- it is not what you would generally choose to do)
Beyond that, you may want to consider passing a FILE* parameter instead of a filename to readFile. Why? if the file cannot be opened and validated in the caller, there is no reason to make a function call to attempt to read it.
To parse the values from each line read from the file, you can use strtok, or you can use a pair of pointers to work your way through the buffer, you can use a combination of strcspn/strspn or you can use sscanf -- which make more sense here. For example, you need (1) to ensure you attempt to fill no more than ROWS sets of values in mArray, (2) you need to read each line, and (3) you need to parse each integer value from your buffer to the elements of mArray. You can actually condition your loop control on all three parameters to ensure a successful read, e.g.
int n = 0;
char buf[MAXC];
/* read/validate 4 integers per-row, protect array bounds */
while (n < ROWS && fgets (buf, MAXC, in) &&
sscanf (buf, "%d,%d,%d,%d", &max->mArray[n][0], &max->mArray[n][1],
&max->mArray[n][2], &max->mArray[n][3]) == 4)
n++; /* increment row count */
In order to make the number of rows read available back in the caller -- a good choice is to return n; instead of return 0;. Making the changes suggested, you could do:
#define ROWS 5 /* if you need a constant, #define one (or more) */
#define COLS 4
#define MAXC 100
typedef struct maximum { /* struct and typedef */
int mArray[ROWS][COLS];
} maximum;
int readFile (FILE *in, maximum *max)
{
int n = 0;
char buf[MAXC];
/* read/validate 4 integers per-row, protect array bounds */
while (n < ROWS && fgets (buf, MAXC, in) &&
sscanf (buf, "%d,%d,%d,%d", &max->mArray[n][0], &max->mArray[n][1],
&max->mArray[n][2], &max->mArray[n][3]) == 4)
n++; /* increment row count */
return n; /* return number of rows successfully read */
}
Adding a main() that validates the return of readFile() to ensure you do not attempt to output more values that you have read, you could do:
int main(int argc, char *argv[]) {
int n = 0; /* row count */
maximum max = {{{0}}}; /* struct with 2d array initialized all zero */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
if ((n = readFile (fp, &max)) != ROWS) /* validate rows read from file */
fprintf (stderr, "warning: %d rows filled in max->mArray.\n", n);
for (int i = 0; i < n; i++){ /* output results */
for (int j = 0; j < COLS; j++)
printf (" %d", max.mArray[i][j]);
putchar ('\n');
}
}
Example Use/Output
With your data in the file named dat/in5x4.csv, your use and results would be:
$ ./bin/struct2darr dat/in5x4.csv
6 4 7 3
4 2 3 2
2 5 3 3
6 3 3 2
5 6 7 5
Look things over and let me know if you have further questions.