Reputation: 209
(This is a follow up to this question.)
I'm trying to understand what "best practice" (or really any practice) is for passing a multidimensional array to a function in C. Certainly this depends on the application, so lets consider writing a function to print a 2d array of variable size. In particular, I'm interested in how one would write the function printArry(_____)
in the following code.
The answer in the question referenced above uses variable length array (VLA) features of C. These are not present in the C98 standard and are optional in the C11 standard (they also don't seem to compile in C++11 either).
How would one answer this question without using VLA functionality? Passing a two dimensional array seems like a fairly basic task, so I can't imagine there isn't a reasonable approach to this.
void printArry(_____){
/* what goes here? */
}
int main(void){
int a1=5;
int b1=6;
int a2=7;
int b2=8;
int arry1[a1][b1];
int arry2[a2][b2];
/* set values in arrays */
printArry(arry1, a1, b1);
printArry(arry2, a2, b2);
return 0;
}
Upvotes: 2
Views: 1086
Reputation: 3754
You would not need a VLA to pass an VLA as parameter. In fact you must know that arrays and functions can be neither passed to nor returned from a function (unless you use a structure hack, but in our case this wouldn't work).
However you could pass a pointer to your array using VM types. Something like this:
void printArry(size_t a1, size_t b1, int (*parry)[a1][b1]){
/* accessing array 'array' by '*parry' */
}
You must declare the pointer to the VLA array parameter after declaring the parameters defining the array dimensions for obvious reasons ('a1' and 'b1' must be defined at the time of use).
The code using this function will look like this:
int main(int argc, char** argv){
size_t a1=5;
size_t b1=6;
size_t a2=7;
size_t b2=8;
int arry1[a1][b1];
int arry2[a2][b2];
/* set values in arrays */
printArry(a1, b1, &arry1);
printArry(a2, b2, &arry2);
}
We can't use the structure hack firstly because structures can't have VLA members and secondly because even if they could we wouldn't be able to specify their length when an argument is passed (the member type must be known at the time of declaration and we can't declare structures inside a function declaration).
Confirmed - even ordinary (non VM) arrays must work with this version of 'printArry'(supposing that their dimensions sizes are properly passed to the function). At least this is what the C11 standard says about it in it's examples.
All pointers to arrays with equal dimensions sizes can be assigned one another (no-matter if they are VM or not) without invoking UB.
Here are the examples on the latest C11 standard from which this comes from:
EXAMPLE 3 The following declarations demonstrate the compatibility rules for variably modified types.
extern int n; extern int m; void fcompat(void) { int a[n][6][m]; int (*p)[4][n+1]; int c[n][n][6][m]; int (*r)[n][n][n+1]; p = a; // invalid: not compatible because 4 != 6 r = c; // compatible, but defined behavior only if // n == 6 and m == n+1 }
At $6.7.6.2.
Which means you can call this function for example like this:
int main(int argc, char **argv)
{
int arry[10][23];
printArry(sizeof(arry) / sizeof(arry[0]),
sizeof(arry[0]) / sizeof(arry[0][0]), &arry);
}
On the other hand you could also access your array the bad old way using a simple pointer to it's first element:
void printArry(size_t a1, size_t b1, int *parryFirstElement){
for(size_t i = 0; i < a1; ++i)
for(size_t y = 0; y < b1; ++y)
parryFirstElement[i * b1 + y]; //your array [i][y] element
}
Calling this function could be done the same way as before (as IIRC pointer in 'C' doesn't need to be casted).
I replaced 'int' in the times where is better to use 'size_t'. Although if you're targeting some old standard you could reverse them back to 'int'.
I may be wrong though. I'm sleepy right now. If I'm wrong - well then that's bad.
Upvotes: 2
Reputation: 754500
The question is currently problematic:
The answer in the question referenced above uses variable length array (VLA) features of C. These are not present in the C98 standard and are optional in the C11 standard.
The C standard prior to C99 (ISO/IEC 9899:1990) was C89 (ANSI) or C90 (ISO) — functionally the same standard. There was C++98 (ISO/IEC 14882:1998), but that's a wholly different language.
int main(void) { int a1=5; int b1=6; int a2=7; int b2=8; int arry1[a1][b1]; int arry2[a2][b2];
This sample code is using VLAs; it will not compile under strict C90 rules.
If you are constrained to the pre-1999 C standard, then you are constrained to using code similar to this. Clearly, you can initialize the arrays by reading data from a file or anything else that takes your fancy; using direct initializers is possible because the arrays are no longer VLAs.
#include <stdio.h>
void printArry(int d1, int d2, int *data);
int main(void)
{
enum { a1 = 5, b1 = 6, a2 = 7, b2 = 8 };
int arry1[a1][b1] =
{
{ 9, 8, 7, 6, 5, 4 },
{ 2, 3, 4, 3, 2, 1 },
{ -9, -8, -7, -6, -5, -4 },
{ 39, 38, 37, 36, 35, 34 },
{ 99, 98, 97, 96, 95, 94 },
};
int arry2[a2][b2] =
{
{ 198, 158, 165, 136, 198, 127, 119, 103, },
{ 146, 123, 123, 108, 168, 142, 119, 115, },
{ 160, 141, 168, 193, 152, 152, 147, 137, },
{ 144, 132, 187, 156, 188, 191, 196, 144, },
{ 197, 164, 108, 119, 196, 171, 185, 133, },
{ 107, 133, 184, 191, 166, 105, 145, 175, },
{ 199, 115, 197, 160, 114, 173, 176, 184, },
};
printArry(a1, b1, &arry1[0][0]);
printArry(a2, b2, &arry2[0][0]);
return 0;
}
void printArry(int d1, int d2, int *data)
{
int i, j;
for (i = 0; i < d1; i++)
{
for (j = 0; j < d2; j++)
printf(" %4d", data[i * d2 + j]);
putchar('\n');
}
}
The printArry()
function is, essentially, doing the index (subscript) calculation that you would like the compiler to do for you — but it can't because it is not able to handle C99 VLAs.
The first array of data is hand-crafted to allow problems to be spotted (such as using d1
instead of d2
in the subscript calculation). The second array of data is simply 56 random values between 100 and 199.
Sample output:
9 8 7 6 5 4
2 3 4 3 2 1
-9 -8 -7 -6 -5 -4
39 38 37 36 35 34
99 98 97 96 95 94
198 158 165 136 198 127 119 103
146 123 123 108 168 142 119 115
160 141 168 193 152 152 147 137
144 132 187 156 188 191 196 144
197 164 108 119 196 171 185 133
107 133 184 191 166 105 145 175
199 115 197 160 114 173 176 184
Upvotes: 3
Reputation: 40645
There seems to be a misunderstanding about the standards and VLAs: C99 and all following C standards contain VLAs, and compilers, such as the gcc
support it (with the -std=c99
, -std=C11
, etc. flags).
What does not work, is C++ of any standard. VLAs are simply not a feature of the language, C is much more powerful in this regard than C++.
So, if you want to use VLAs, just make sure that you are compiling with a real C compiler (gcc
), not with a C++ compiler (g++
). If you do so, they should work like a charm.
Afaik, there is a proposal for C++17 that says it wants to add VLAs to the language. However, the VLA functionality in that proposal is seriously restricted. Up to the point of complete unusability in my eyes. C99 still remains much more powerful than C++17 in that regard.
Upvotes: 0