0

I'm working my way through 88 C Programs, relearning C after almost twenty-five years away (complicated by the fact there have been at least two major version revisions to the language itself since 1990, and I'm sure the Turbo-C I used then wasn't fully C89 compatible). I don't recall that the class I took did anything of consequence with two-dimensional arrays, and passing those into functions, with anticipation that I'll need to work on data without knowing the dimensions at compile time, is well along to making my head explode.

I'm using gcc (the version currently found in the standard repositories for Ubuntu 14.04), which I gather is supposed to support variable-length array declarations under C99 or C2011 standards, but the declarations I'm trying to use to make the function recognize the array without having to know the size at compile time are getting errors about "conflicting type". I'm compiling with warning set to max, using a tiny Python program to save having to type a long command line every time I need to compile (resulting command is gcc -std=c99 -pedantic -Wall -Wextra -o target target.c -lm). Here are the prototype, caller lines, and function definition (the code is full of warning-inducing bad practices, too, where I copied it from the book; I'm not concerned about those at the moment, I'm interested in learning the sane way to do this so I don't have to use those ugly, confusing methods).

#include <stdio.h>
#include <stdlib.h>

/* ANSI prototypes */

void s2 (int, int, int*);
int main (void);

int main (void)
{
  int one[5]={0,1,2,3,4};
  int two[3][4] =
  {
{0,1,2,3},
{10,11,12,13},
{100,101,102,103}
  };

  /* try to call s2, it won't work */
  printf("\ncalling s2 \n");
  s2(1, 5, one);
  s2(3, 4, two); /* will be compiler warning */
}

void s2(int rows, int cols, int *x[rows][cols])
{
  /* wants to use multiple square brackets; needs dimension info */
  int i,j;
  for (i=0; i<rows; i++)
  {
     for (j=0; j<cols; j++)
       printf("%4i", x[i][j]);
  }
}

This gives the following string of warnings and errors:

./gccm sotest
sotest.c: In function ‘main’:
sotest.c:22:3: warning: passing argument 3 of ‘s2’ from incompatible pointer type [enabled by default]
  s2(3, 4, two); /* will be compiler warning */
  ^
sotest.c:6:6: note: expected ‘int *’ but argument is of type ‘int (*)[4]’
void s2 (int, int, int*);
  ^
sotest.c: At top level:
sotest.c:25:6: error: conflicting types for ‘s2’
void s2(int rows, int cols, int *x[rows][cols])
  ^
sotest.c:6:6: note: previous declaration of ‘s2’ was here
void s2 (int, int, int*);
  ^
sotest.c: In function ‘s2’:
sotest.c:32:7: warning: format ‘%i’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
  printf("%4i", x[i][j]);
  ^

I've spent at least a couple hours digging through other similar-seeming questions here, reading articles on the Web, and generally trying to wrap my brain around the insanity that arises as soon as you need to pass a two-dimensional array to a function in C -- it's so simple in many other languages, and one-dimensional arrays are easy enough in C! I've found nothing that makes sense; the only sensible suggestion I've seen is to wrap the array in a Struct, but those surely didn't exist for the first twenty or so years C was in wide use and I'm not sure how/why that makes it better.

Zeiss Ikon
  • 481
  • 4
  • 13
  • 2
    possible duplicate of [C pointer to two dimensional array](http://stackoverflow.com/questions/14808908/c-pointer-to-two-dimensional-array) – OldProgrammer Dec 20 '14 at 21:55
  • Did you take a look at the countless similar questions here? – 2501 Dec 20 '14 at 21:55
  • @2501 the fact they're countless makes it impossible in finite time to look at all of them, but the sample I did examine (those on the first 2-3 search pages with titles that looked relevant) didn't offer an answer to what I'm asking -- despite reading several of those in their entirety. – Zeiss Ikon Dec 20 '14 at 21:59
  • `struct` has been part of the language since the beginning (K&R). Doesn't address "how/why that makes it better," just had to point that out for anyone using one of the various really-old-but-free compilers available on the web. – frasnian Dec 20 '14 at 23:28
  • Late follow up -- using `struct` for this makes everything _vastly_ simpler; a struct passes just like a one-dimension array for most purposes (and it's easy to figure out from the compile errors the few occasions where a pointer is needed). Doesn't do much for Variable Length Arrays, but there doesn't seem to be a lot of simplification available there. – Zeiss Ikon Dec 29 '14 at 12:15

3 Answers3

5

Your declaration should look like this:

void s2(int rows, int cols, int x[rows][cols]);

That function takes a two-dimensional array of integers. With that declaration your call with variable two will be OK. Your call with variable one is wrong as one only has one dimension.

Also, make sure that your declaration and function match, your function then needs to look like this:

void s2(int rows, int cols, int x[rows][cols]) {

Henrik Carlqvist
  • 1,138
  • 5
  • 6
  • could also go with `void s2 (int, int, int[][*]);` – Ryan Haining Dec 20 '14 at 22:01
  • Aha! I generally don't need/want my prototype to be that specific. Let me try that... – Zeiss Ikon Dec 20 '14 at 22:01
  • @RyanHaining -- that's as bad as the ones I'm trying not to use, in terms of making my brain hurt... – Zeiss Ikon Dec 20 '14 at 22:02
  • 1
    the `[*]` means it will be decided by something that is not currently available, since you don't seen to want to name your parameters that'd be my recommendation unless you decide to name them – Ryan Haining Dec 20 '14 at 22:03
  • @RyanHaining I don't have anything against naming parameters in my prototypes, I just learned prototypes without names and they work fine that way until I start to deal with arrays of more than one dimension. – Zeiss Ikon Dec 20 '14 at 22:21
  • @HenrikCarlqvist your change quieted all the errors and even warnings (after I got rid of reference to array `one`) -- but now something is preventing `s2` from stepping through the columns. I presume that's a subtle logic error in the nested `for` loops and I can fix that myself... – Zeiss Ikon Dec 20 '14 at 22:24
2

Here's the full code using the new VLA feature. The only trick is that you need to pass the address of array one. Since one is a 1D array, the address of one is a row pointer, i.e. it points to an array of rows (albeit an array of only one row). Array two is already an array of row pointers, by virtue of the fact that array two is a 2D array.

Yeah, I know, that's not a great explanation. If you have an old copy of K&R 2nd edition laying around, row pointers are explained (badly) in section 5.7 and 5.9.

#include <stdio.h>
#include <stdlib.h>

void s2( int rows, int cols, int array[rows][cols] );

int main (void)
{
    int one[5]={0,1,2,3,4};
    int two[3][4] =
    {
        {0,1,2,3},
        {10,11,12,13},
        {100,101,102,103}
    };

    printf( "array one\n" );
    s2( 1, 5, &one );
    printf( "array two\n" );
    s2( 3, 4, two );
}

void s2( int rows, int cols, int array[rows][cols] )
{
    int i, j;

    for ( i = 0; i < rows; i++ )
    {
        for ( j = 0; j < cols; j++ )
            printf( " %3i", array[i][j] );
        printf( "\n" );
    }
}
user3386109
  • 34,287
  • 7
  • 49
  • 68
  • That explanation makes *at least* as much sense as any of the ones in the answers to not-quite-duplicate questions that I found. Of course, many of those answers insist that a 2-D array is not an array of pointers, but some of them admit that we can sometimes pretend it is. I also find it encouraging that your code above is essentially identical to what I got by implementing Henrik's answer (and fixing my stray semicolon that was preventing `s2` from stepping through the columns). – Zeiss Ikon Dec 20 '14 at 23:01
  • @ZeissIkon Yes, the difficulty arises because of the way C treats pointers and arrays. Arrays decay to pointers (whatever that means), and pointers can be used with array syntax. For 1D arrays, it's not too bad because the syntax is fairly clean. But 2D arrays are difficult to explain because the syntax for declaring a row pointer is a mess, and the pointer math for row pointers is outside the realm of everyday experience. Hence, it's one of those things where I know how to use it, but I have a heck of a time trying to explain it. – user3386109 Dec 20 '14 at 23:20
  • **1-D arrays** decay from `int array[x]` to `int *array` when passed to a function. **2-D arrays** decay from `int array[x][y]` to `int (*array)[y]`. – David C. Rankin Dec 20 '14 at 23:27
0

Though not an optimal solution, you can rely on the fact that arrays are stored sequentially in memory to treat your multi-dimensional matrices as a single-dimension array. This is possible because you are already passing the array size to the function, which allows for an easy calculation of the address offset for each element in the array. (regardless of how many dimensions). It requires a cast in the call:

#include <stdio.h>
#include <stdlib.h>

/* ANSI prototypes */

void s2 (int, int, int *);
int main (void);

int main (void)
{
    int one[] = { 0, 1, 2, 3, 4 };
    int two[][4] = {
        {0, 1, 2, 3},
        {10, 11, 12, 13},
        {100, 101, 102, 103}
    };

    printf ("\ncalling s2 for 'one'\n");
    s2 (1, 5, (int *)one);

    printf ("\ncalling s2 for 'two'\n");
    s2 (3, 4, (int *)two);      /* will be compiler warning - fixed */

return 0;
}

void s2 (int rows, int cols, int *x)
{
    /* wants to use multiple square brackets; needs dimension info */
    int i, j;
    for (i = 0; i < rows; i++) {
        for (j = 0; j < cols; j++)
            printf ("%4i", x[i * cols + j]);
        printf ("\n");
    }
}

output:

$ ./bin/multidimasz

calling s2 for 'one'
   0   1   2   3   4

calling s2 for 'two'
   0   1   2   3
  10  11  12  13
 100 101 102 103

It also required removing the extra ; behind your second for loop :).

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • As I understand it, contiguous memory allocation is either implementation dependent or completely undefined; Henrik's answer below provided a solution I'm more comfortable with (and I did find the extra semicolon about one minute before your answer went up). – Zeiss Ikon Dec 20 '14 at 23:14
  • I agree that is why I qualified the answer with `this is not an optimal solution`. Good look with your relearning. – David C. Rankin Dec 20 '14 at 23:16
  • @ZeissIkon There is a good FAQ here on [**C99 faq and Variable Length Arrays**](http://stackoverflow.com/questions/3082126/c99-faq-and-variable-length-arrays). It can fill in the subtleties of the discussion. – David C. Rankin Dec 20 '14 at 23:32