1

How should an array of constant size:

const int m = 5, n = 3;
int arr[m][n];

be passed to a function in a way which is both C89 and C++-compatible?

void func(const int m, const int n, int arr[][n]) { }

isn't valid C++ (giving errors such as "A parameter is not allowed" and "Variable 'n' was not declared in this scope"), even though the size of arr is determinate at compile-time. (It is valid C, however.) #defineing m and n works but is not preferred due to scope issues. Passing a pointer to the first element of the array leads to ugly code in the function body.

Feel free to take a look at this FAQ for context.

1''
  • 26,823
  • 32
  • 143
  • 200
  • What do you mean by *isn't allowed*? Show a small program that exhibits the problem, and the error message that gets generated from it. – jxh Jul 17 '13 at 00:02
  • Check [this answer](http://stackoverflow.com/questions/17566661/c-dynamic-array-initalization-with-declaration/17567663#17567663) – Grijesh Chauhan Jul 17 '13 at 02:13
  • @GrijeshChauhan Thanks, but all of those cases are legal C++ as well. The issue here is different: m and n are technically constant, but the compiler sees them as variables. – 1'' Jul 17 '13 at 02:32

5 Answers5

3

In C++, you can pass an array to a function with full type information intact by utilizing a template and an array reference function argument:

template <unsigned M, unsigned N>
void func (int (&arr)[M][N]) {
    //...
}

The function prototype you are using is using a C99 feature called VLA to provide a dynamic binding of the array dimension. This is not a C++ feature, although some C++ compilers will allow it as an extension to the C++ language.

The C-FAQ was written before C99 was ratified, so the variable length array feature was not yet a standard feature of C. With a modern C compiler with VLA support, the function prototype you provided works just fine.

There is another alternative to use if you have an older compiler for which VLA support is not available. That is to treat the 2-D array as a flattened 1-D array, and use manual calculations to index the correct integer:

void func(const int m, const int n, void *p) {
    int *a = p;
    int i, j;

    for (i = 0; i < m; ++i) {
        for (j = 0; j < n; ++j) {
            printf(" %d", a[i*n + j]);
        }
        puts("");
    }
}

Then you call func(m, n, arr). In side the function, the expression

a[i*n + j]

steps over n ints i times, then steps over j ints. Since each row is n ints long, the calculation returns the ith row and the jth column, which corresponds precisely to arr[i][j].

jxh
  • 69,070
  • 8
  • 110
  • 193
  • Actually, my code is valid C89 and C99, just not valid C++. My mistake - see the edited question. – 1'' Jul 17 '13 at 00:21
  • It's actually not valid C89, as C89 does not allow VLA. I will answer the C++ part of it now. – jxh Jul 17 '13 at 00:22
  • I was able to compile this [here](http://ideone.com/EFSQjF) in C89 mode, since it's not actually a VLA. The template solution is probably best, though not C-compatible, which was my original intention. – 1'' Jul 17 '13 at 00:28
  • 1
    @1'': That's not really C89 mode. You have to feed `gcc` the command line parameter `-std=c89` or `-std=c90` to see an error. – jxh Jul 17 '13 at 00:30
2

I have tried this code:

void func(const int m, const int n, int arr[][n])
{
  printf("%d\n", arr[4][2]);
}

int             main()
{
 const int m = 5, n = 3;
 int arr[m][n];

 arr[4][2] = 10;
 func(m, n, arr);
}

and this work with no warnings

Saxtheowl
  • 4,136
  • 5
  • 23
  • 32
0

The problem here is the "missing" support for dynamic arrays in C++.

const int m = 5, n = 3;
int arr[m][n];

Works since m and n are compile time constant and accessible directly at the declaration of the array.

void func(const int m, const int n, int arr[][n]) { }

The compiler handles your function regardless of where it is called in first place. Therefore n is unknown/variable and thus prohibited as a array dimensionality.

The following example won't work too because of the very same reason:

void foo (const int n)
{
  int arr[n]; // error, n is const but not compile time constant
}

int main (void) 
{ 
  foo(4);
}

jxh answered what to do about it.

Community
  • 1
  • 1
Pixelchemist
  • 24,090
  • 7
  • 47
  • 71
0

Your array arr[m][n] is not constant. However you have constant variables M and N. You should also define the arr[m][n] as a constant and not just an int array.

Juniar
  • 1,269
  • 1
  • 15
  • 24
0

You may want to consider dynamicaly allocating your array so that you can just pass the pointer address down.

const int m = 5, n = 3;
int i = 0;
int* *arr; //Pointer to an integer pointer (Note can also be int **arr or int** arr)

arr = malloc(sizeof(int*)*(m+1)); //I add one because I am assuming that 'm' does not account for the terminating null character. But if you do not need a terminating null then you can remove this and the perantheses around the 'm'.

for(i = 0; i < m; i++)
{
    arr[i] = malloc(sizeof(int*)*(n+1)); //Same as before
}

The inital malloc() call allocates memory for an array of integer arrays or said in another way, it allocates a pointer to a series of other pointers. The for loop will allocate an integer array of 'm' size for each element of the original array or said another way it will allocate space for every pointer address pointed to by the original pointer address. I left out error checking in order to simplfy my example but here is the same example with error checking.

const int m = 5, n = 3;
int i = 0;
int* *arr = NULL;

if((arr = malloc(sizeof(int*)*(m+1))) == NULL)
{
   perror("ERROR(1): Failed to allocate memory for the initial pointer address ");
   return 1;
}

for(i = 0; i < m; i++)
{
   if((arr = malloc(sizeof(int*)*(m+1))) == NULL)
   {
      perror("ERROR(2): Failed to allocate memory for a subsequent pointer address ");
      return 2;
   }
}

Now that you have dynamicaly allocated your array you can just pass the pointer address. int* *arr in the following the way.

void fun(const int n, const int m, int* *arr) {}

Also you don't necessarily have to keep track of the size of your arrays if the sizes are constant and if you use null terminated arrays. You just have to malloc the array using the constant integer variable's actual value and then check for the terminating null byte when iterating threw the array.

int* *arr = NULL;

if((arr = malloc(sizeof(int*)*6)) == NULL)'m'+1 = 6;
{
   perror("ERROR(1): Failed to allocate memory for the initial pointer address ");
   return 1;
}

for(i = 0; i < m; i++)
{
   if((arr = malloc(sizeof(int*)*4) == NULL)//'n'+1 = 4
   {
      perror("ERROR(2): Failed to allocate memory for a subsequent pointer address ");
      return 2;
   }
}

You can then display the entire two dimensional array in the following way. Note that '\000' is the octagonal value for a null byte(00000000).

int i, j;
for(i = 0; arr[i] != '\000'; i++)
{
    for(j = 0; arr[i][j] != '\000'; j++)
    {
      printf("%i ", arr[i][j]); //Prints the current element of the current array
    }

    printf("\n"); //This just ends the line so that each of the arrays is printed on it's own line. 
}

Of course the above mentioned loops would have the same result as the following.

int i, j;
int m = 5;
int n = 3;

for(i = 0; i < m; i++)
{
    for(j = 0; i < n; j++)
    {
      printf("%i ", arr[i][j]); //Prints the current element of the current array
    }

    printf("\n"); //This just ends the line so that each of the arrays is printed on it's own line. 
}

Which means, in most situations there is no need for keeping track of an array's size but there are situations in which it is necessary. For example if one your arrays could possible contain a null byte other than the terminating null byte. The new null byte would shorten the array's size to the index of the new null byte. If you have any questions or comments feel free to comment below or message me.

John Vulconshinz
  • 1,088
  • 4
  • 12
  • 29
  • Since we're talking about int arrays, how is a null byte different from the number 0? – 1'' Jul 17 '13 at 02:00
  • A null byte is a byte in which all of it's bits are 0. A 0 is a specific byte. I know that when we are talking about characters there is a definite difference. You should look up decimal or hexadecimal but the easy answer would be an simple example. Do the following – John Vulconshinz Jul 20 '13 at 04:46
  • char foo = 0; if(foo == '0') printf("foo == '0'\n"); if(foo == 0) printf("foo == 0\n"); foo = '\000'; if(foo == 0) printf("foo == 0\n"); if(foo == '0') printf("foo == '0'); foo = '0'; if(foo == '0') printf("foo == '0'); if(foo == 0) printf("foo == 0\n"); – John Vulconshinz Jul 20 '13 at 04:52
  • The answer is that there's no difference between the integer literal `0` and the null byte `\0`, and also no difference between the integer literal `48` and the 0 "character literal" (used loosely - they're all integer literals) `'0'`. One reason it's such a bad idea to null-terminate integer arrays is that you would lose the ability to store the number 0 in the array. Another is that you lose the ability to safely index the array because you don't know how long it is. – 1'' Jul 20 '13 at 14:00
  • There is a difference between a null byte and a zero byte when we are talking about 8 bit ASCII bytes. I do not know if there is a difference when it comes to an integer data type. I only suggested using null terminated strings because I thought you were less experienced than you are. I simply wanted to show you there is more than one way to iterate a string but you obviously already know this. So I apologize for my stupid response because it is completely irrelevant. – John Vulconshinz Jul 20 '13 at 21:16
  • That's all right. Yes, the value of the byte that represents the zero character (48) is different than the value of the null byte (0). An integer 0 is just 0, though. – 1'' Jul 20 '13 at 21:29