Matrices as function parameters in C89
Asked Answered
G

4

6

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:

  1. How you would access elements in this matrix inside the function. Can you still use the notation mat[i][j]?
  2. 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.
  3. 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?

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?

Glomerule answered 19/1, 2020 at 16:8 Comment(3)
1: Yes. 2: depends on what you need to do, you don't necessarily need malloc(). 3: Yes, when passed as an argument, a matrix decays into a pointer to pointer, just like an array decays into a pointer.Root
Thank you. To answer (2): let's suppose I need to get the matrix's dimensions from stdinput, then I need to create a matrix with such dimensions, and then I need to fill it with data given by the user. I would use for(size_t i = 0; i < rows; i++) { for(size_t j = 0; j < cols; j++) { scanf("%d", &mat[i][j]); /*using scanf as an example, could be anything*/ } } I suppose that in this case I would have to allocate a new element for each spot in the matrix?Glomerule
#42094965 and especially Lundin's answer there.Blades
G
8

However, I'm having issues understanding:

  1. How you would access elements in this matrix inside the function. Can you still use the notation mat[i][j]?
  2. 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.
  3. 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.

Gee answered 19/1, 2020 at 16:35 Comment(2)
VLAs are not the same as an array of pointers to rows. In C89 such an array passed to a function, cannot be addressed as mat[i][j] (an array of pointers to rows can be addressed like that). Still a very complete answer.Ginetteginevra
Thank you for the thorough answer! I have posted a question in which I show a program I made that uses those principles: #59829637 this was the reason why I asked this question. If you want to take a look at it an give me advice, it's very welcomeGlomerule
R
4
  1. How you would access elements in this matrix inside the function. Can you still use the notation mat[i][j]?

Yes.

  1. 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.

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);
  1. 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().

Root answered 19/1, 2020 at 16:33 Comment(0)
G
1

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.

Ginetteginevra answered 19/1, 2020 at 16:30 Comment(12)
That's completely wrong and only works if the matrix is contiguous in memory, which is not guaranteed by the signature of the function in any way. A dynamically allocated matrix will 100% crash the above code.Root
You can indeed flatten then the array into a one-dimensional contiguous structure, provided that you also create the array using the same logic. Your solution will also work for a real 2D array declared as a 2D array, since the elements are contiguous. But this will not work with a pointer of pointer, because a pointer of pointer has a different layout. Memory corruption guaranteed. It would be nice to edit your answer to draw OP's attention to the cases where your solution works, and the different memory layouts.Woolworth
@MarcoBonelli, this also works for for a dynamically allocated matrix. It will not work for an "array of pointers to arrays of row-data", but that is not equivalent to VLAs, which was the core of the question. VLAs are contiguous in memory by definition.Ginetteginevra
@Christophe, see my reaction to Marco's comment: a VLA is contiguous in memory by definition. VLA equivalence was the core of the question.Ginetteginevra
@PaulOgilvie it will never work for a dynamically allocated matrix. Test it yourself. "Array of pointers to arrays" is exactly what a dynamically allocated matrix is.Root
@MarcoBonelli, My understanding of a dynamically allocated array, in whatever form, is an array allocated with malloc. However, your definition is an "array of pointers to rows of data", which is (again) not equivalent to a VLA.Ginetteginevra
@MarcoBonelli, an "array of pointers to arrays of row-data" would be required to be passed as an int **, not an int *.Ginetteginevra
@PaulOgilvie you specifically said: "this also works for a dynamically allocated matrix". It does not. That's what I was referring to. Of course if works for an array (which is not a matrix).Root
@MarcoBonelli, we have a difference of terms used, not a difference of understanding, I believe.Ginetteginevra
@PaulOgilvie well of course if you say "matrix" when you actually mean "array" I can't read your mind :')Root
@MarcoBonelli, Well, my answer is clear. I never used the word "dynamic" nor "matrix" in my answer (before edit) and noted that your array of pointers to rows requires a int ** parameter, not an int * parameter. So there cannot be any confusion.Ginetteginevra
I think, Paul and Marco, that you have just two different alternatives to solve the same problem and that both have their pros and cons: Marcos int** has the advantage of naturally working with 2D native indexing scheme (but requires more complex initialization), whereas Paul’s solution has the advantage of more efficient memory management (but is incompatible with build-in 2D). Personally I have seen both options within K&R and C89 code bases. The only problem here is the first sentence which might misguide OP in thinking that int** is like int*.Woolworth
K
1

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++

Kautz answered 19/1, 2020 at 16:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.