For most of my undergrad C programming course, we studied C99 and our lecturer never bothered to teach us the main differences between C99 and previous versions.
We have recently been informed that there's a possibility we'll be asked to implement solutions using C89 during our next exam, instead.
My question regards the use of variable-length multidimensional arrays with regards to declaration and usage inside a function.
In C99, I can have a function like this:
void func(int cols, int mat[][cols], int rows);
Whereas in C89, VLA's and similar contraptions aren't legal. I have been told that you would need to use a pointer to pointer instead. So something like:
void func(int **mat, int cols, int rows);
However, I'm having issues understanding:
mat[i][j]
?int **mat;
? I'm thinking you'd have to use malloc()
, but I'm having a hard time figuring out the exact declaration statement.Side question. Still regarding variable-size matrices, I've been told that having a setup like this:
int rows, cols;
// get rows and cols from stdinput
int mat[rows][cols];
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?
Thank you!
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()
.
How would you access the elements of this matrix inside the function. Can you still use the notation mat[i][j]?
Yes, you can use the array-like notation to access the value pointed by a pointer.
Is an array name a pointer?
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.
This is your choice. If you are in a scenario where static allocation is required then you can use the normal array declaration. If you don't know the matrix dimensions beforehand then you should use dynamic allocation. In the latter case, you should declare your matrix in the pointer syntax so that it could store the address returned by malloc.
How do I work with dynamic multi-dimensional arrays in C?
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?
No worries in this case because an array eventually decays into a pointer when received by functions.
Isn't this the best way to go about creating a matrix with given dimensions, due to allocation on the program stack. What's a better way?
This question is synonymous to Static array vs. dynamic array in C++
In C89 if you have a function like void func(int **mat, int cols, int rows)
, you address the elements as follows, however, this should be a single pointer:
int main(void)
{
int arr1[10][10]; // automatic array, allocated on the stack
int *arr2= malloc(100*sizeof(int)); // dynamic array, allocated on heap
func(arr1, 10, 10); // pass to func.
func(arr2, 10, 10); // pass to func. Same for both arrays
return 0;
}
void func(int *mat, int cols, int rows)
{
// address element x, y, with x the column number and y the row number:
int k= mat[cols*y + x];
As the compiler doesn't know anything about rows and columns, you do that calculation yourself. So you first skip y
rows of cols
columns and then take the x
'th element.
EDIT:
This works for arrays that are contiguous blocks of memory, which is equivalent to the VLAs of the modern C standard.
Another way is the "array of pointers to rows of data", but that is not equivalent to a VLA, which was your question. Other answers discuss this type of solution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With