Matrices as function parameters in C89
However, I'm having issues understanding:
- How you would access elements in this matrix inside the function. Can you still use the notation mat[i][j]?
- How you would declare and populate such a matrix. Do you begin with something like int **mat;? I'm thinking you'd have to use malloc(), but I'm having a hard time figuring out the exact declaration statement.
- Can matrices even be used like pointers to pointers like that? I have read that they work similarly but not exactly the same. How do you solve the outlined problem in C89?
1. mat[i][j]
In C89, you are correct, you have no support for VLAs unless provided by a non-standard compiler extension (gcc does so). However, you can accomplish the same in two different arrays.
If you know the number of columns you will have at compile time and can define a constant for that value, then you can declare a pointer-to-array [COLS]. For example, if you know you will have 32 columns and an unknown number of rows, you can do:
#define COLS 32
...
int (*array)[COLS] = malloc (rows * sizeof *array);
That will allocate a block of memory in a single call providing storage for rows
number of int[32]
arrays allowing you access as array[i][j]
just as before. The beauty of using a pointer-to-array is you have a single-allocation and single-free. You can realloc
the number of rows as needed.
(note: as @PaulOgilvie points out, there is a difference in how you can pass the pointer to array to a function. You cannot pass as int array[][cols]
as with a VLA, you must pass as int (*array)[cols]
-- which you can also use with a VLA, but the reverse does not hold true)
Your other option is the declare a pointer-to-pointer-to type
(e.g. int **array;
). Note there is NO array involved here, it is simply a single pointer to pointer to type. Here allocation is a 2-step process. You first allocate memory for some number of pointers (rows number of pointers). For example:
int **array = malloc (rows * sizeof *array);
Above you allocate a block of memory capable of holding rows
number of pointers to which you can then separately allocate and assign blocks of memory to hold any number of integer values (there is no need that each row point to a block with the same number of integer values -- making a "jagged array" possible, for lack of better words) To then allocate storage for integers values (or whatever type you are using), you would do:
for (int i = 0; i < rows; i++)
array[i] = malloc (cols * sizeof *array[i]);
(note: you must validate every allocation which has been omitted for brevity. Also note in both cases above the dereferenced pointer has been used to set the typesize for allocation, e.g. malloc (rows * sizeof *array)
which could have been malloc (rows * sizeof(int*)))
. If you always use the dereferenced pointer to set typesize -- you will never get the type-size wrong)
At this point you have a pointer to a block of memory storing rows
number of pointers, and then you have assigned a block of memory capable of holding cols
number of integer values which you can access as array[i][j]
. Additionally, here you can realloc
the block of memory providing rows
pointers to add rows any time you need, but you have to allocate storage for integer values as well and assign those allocated blocks to your new row pointers before you attempt to store values there.
When you are done with your simulated 2D array based on a pointer-to-pointer you have a 2-step free as well. You must free the allocated blocks storing integers before you can free the block holding your rows pointers, e.g.
for (int i = 0; i < rows; i++)
free (array[i]); /* free storage for integers */
free (array); /* free pointers */
2. Populating Either Object
In either case since you can access your simulated 2D array with array[i][j]
notation, you can now fill and access the values in array
just as you did with a 2D VLA under C99+.
3. Can Matricies Be used with Pointers to Pointers
Yep, the simulated 2D array provides the exact same functionality as described above.
- How you would access elements in this matrix inside the function. Can you still use the notation
mat[i][j]
?
Yes.
- How you would declare and populate such a matrix. Do you begin with something like
int **mat;
? I'm thinking you'd have to usemalloc()
, but I'm having a hard time figuring out the exact declaration statement.
If the size of the matrix is not known at compile time, or in general it's a big size, then malloc()
is the way to go. Something like this:
// assume nrows and ncols are dynamic
size_t nrows = /* ... */;
size_t ncols = /* ... */;
size_t i;
int **matrix;
matrix = malloc(nrows * sizeof(int*));
if (matrix == NULL) {
perror("malloc() failed");
exit(1);
}
for (i = 0; i < nrows; i++) {
matrix[i] = malloc(ncols * sizeof(int));
if (matrix[i] == NULL) {
perror("malloc() failed");
exit(1);
}
}
/* fill the matrix */
/* use the matrix however you want */
func(matrix, nrows, ncols);
/* free the allocated memory once you don't need it anymore */
for (i = 0; i < nrows; i++)
free(matrix[i]);
free(matrix);
- Can matrices even be used like pointers to pointers like that? I have read that they work similarly but not exactly the same. How do you solve the outlined problem in C89?
Yes, they can. An array decays into a pointer when passed to functions like this. The same goes for matrixes, which decay into pointer to pointer. See What is array decaying.
Side question [...] isn't the best way to go about creating a matrix with given dimensions, due to allocation on the program stack. what's a better way?
Yes, that's right, that's not the best way. In general programs have a limited stack size, therefore allocating big arrays on the stack is not a good idea. In some occasions, you could exceed the available memory allocated for stack usage and your program will then crash. The best way in this case is to use dynamic allocation through malloc()
.