1

I am trying to pass a 2D array of variable size to a function to print it. I know how it's done when one has fixed sized arrays. But how does one do this with variable sized arrays? Here is some code that'll give you an idea what I am trying to do:

void print_grid(char (*g)[9], int size) // the array subscript (9 here) be variable
{
   int i, j;
   for (i=0; i<size; i++)
   {
      for (j=0; j<size; j++)            
         printf("%c ", g[i][j]);
      printf("\n");
   }
}

I'll call this function using the following code:

char a[3][3], b[9][9];
// assign a, b
print_grid(a, 3);
print_grid(b, 9);

Is there any way to do this without allocating any dynamic memory in print_grid()?

nitzs
  • 259
  • 4
  • 9

4 Answers4

5
void print_grid(int rows, int cols, char g[][cols]) { ... }
R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • Is the value of 'cols' in 'g[][cols]' passed by the calling statement? I did not understand how is this variable? – nitzs Dec 24 '10 at 10:24
  • Yes, the caller is required to pass it. An array in itself does not contain any information about its type. – R.. GitHub STOP HELPING ICE Dec 24 '10 at 13:08
  • It does not even contain information that it is an array... and BTW the above answer does not compile. Only C++ compilers could deduce some `cols` as template argument; but that's another liga. I assume the function is supposed to be called as `print_grid(2, 3, a)` on some `a` def'd as `a[2][3]`. But why `char g[][cols]` declaration? `a` will decay into a `char*`, and the dimensions are always ignored. And this even makes a lot of sense, because the `char*` value is not a pointer-to-array, but a pointer-to-first-element. – Andreas Spindler Feb 16 '12 at 15:44
  • @AndreasSpindler: `char g[][cols]` is equivalent to `char (*)[cols]` which is a pointer to an array of `cols` elements (a variably-modified type). `g` points to the first "row" of the grid, not to a single `char`. – R.. GitHub STOP HELPING ICE Feb 16 '12 at 17:48
  • `cols` is defined at runtime, and the compiler has no chance to declare `print_grid`. Each array you pass to a function decays into a pointer to its first element - known as the "K&R decay convention". – Andreas Spindler Feb 16 '12 at 17:48
  • It's perfectly legal C. In a prototype where `cols` is not available, the syntax is `void print_grid(int, int, char g[][*]);` (note the `*`). Only the actual function *definition* (not declaration) needs to know the parameter `cols` on which the variably-modified type is modified. – R.. GitHub STOP HELPING ICE Feb 16 '12 at 17:50
  • I do not understand `*`. Is this a placeholder for some integer >= 1? – Andreas Spindler Feb 16 '12 at 17:54
  • It's just a syntactic element to mark the variably-modified type. In a sense, yes, it's a placeholder in that it means that in the function definition there will need to be an integer expression in its place. – R.. GitHub STOP HELPING ICE Feb 16 '12 at 18:01
  • e.g. `void print_grid(int, int, char g[][1]);` is legal C. – Andreas Spindler Feb 16 '12 at 18:02
  • @AndreasSpindler: That's also legal C, but it declares something very different: A function that takes a pointer to an array consisting of a single `char`. The `cols` (or `*` in the prototype) is essential for the code to answer OP's question. – R.. GitHub STOP HELPING ICE Feb 16 '12 at 23:45
  • This pointer to a single char is actually a pointer to an `(row*cols)` char field - the "flexible array notation". I think what you mean with `*` is something like `void print_grid(int, int, char (*)[3]);` and then `char g[2][3];` and then `print_grid(2, 3, g)`. Right? So your `*` indeed is a placeholder for some integer constant >= 1 (or >= 0 with the GNU compiler). We cannot circumvent that this constant has to be specified at both, compile-time and runtime. – Andreas Spindler Feb 17 '12 at 08:44
  • No, you continue to misread my answer. The version I wrote, with `cols` and with a literal `*` character in the prototype (if you need a prototype) is correct C and is meant to be used exactly as written. Putting a constant number like 3 in place of that defeats the whole purpose. Why don't you go try it or read the language specification rather than continuing to argue from a misinformed perspective? – R.. GitHub STOP HELPING ICE Feb 17 '12 at 15:38
  • @R..: Your answer doesn't compile (error: cols not declared in this scope). I'm using gnu g++ to compile. – Rushil Paul Jun 01 '14 at 14:08
  • @Rushil: This question is about C, not C++. Would you complain that code in an answer to a Python question doesn't work in a Ruby program? – R.. GitHub STOP HELPING ICE Jun 01 '14 at 14:13
  • Oh I forgot it was about C! BTW is there anything like this for C++ too? – Rushil Paul Jun 01 '14 at 15:52
1
void print_grid(char *g, int size) 
{
   int i, j;

   for( i = 0; i < size; i++)
      for( j = 0; j < size; j++)
      {
         printf("%c ", *(g + i*size + j));
         printf("\n");
      }
}

print_grid(a, 3);
Saeed Amiri
  • 22,252
  • 5
  • 45
  • 83
1

When you pass an array in C it is always passed by reference, i.e. through a pointer. The type of this pointer is not pointer-to-array, but pointer-to-first-element. For example, the code generator will handle void f(char[][10]) as if it where void f(char*). The array dimensions are lost. The parser, however, will complain if it sees f declared twice so.

The motivation behind C was to have a powerful and portable assembler, not a new programming language. Multidimensional arrays are logical constructs that do not exist in the machine. Arrays are a way of thinking.

To pass the dimensions of arrays to functions C programmers traditionally use structs:

typedef struct array_tag {
    int count;
    char data[1]; /* actually data[count] */
} ARRAY;

typedef struct grid_tag {
    int rows, columns;
    char grid[1][1]; /* actually grid[rows][columns] */
} GRID;

void f(ARRAY* x)
{
    int i;
    for (i = 0; i < x->count; ++i) {
        char c = x->data[i];
    }
}

void g(GRID* x)
{
    int i, j;
    for (i = 0; i < x->rows; ++i)
        for (j = 0; j < x->columns; ++j) {
            char c = x->grid[i][j];
        }
}

void h()
{
    {
        const int len = 100;
        ARRAY* x = (ARRAY*) malloc(sizeof(ARRAY) + len * sizeof(char));
        x->count = len;
        f(x);
    }

    {
        const int rows = 2, cols = 3;
        GRID* x = (GRID*) malloc(sizeof(GRID) + rows * cols * sizeof(char));
        x->rows = rows;
        x->columns = cols;
        g(x);
    }
}

Yes, the malloc expression in this example allocates few bytes too much. Therefore the GNU-compiler supports arrays of zero length for a long time, which are not allowed in C90.

C99 has gone one step further with flexible arrays. From ISO/IEC 9899:1999, Section 6.7.2.1, paragraph 16: "As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member." In C99 the ARRAY and GRID types can be declared as:

typedef struct array_tag {
    int count;
    char data[]; /* actually data[count] */
} ARRAY;

typedef struct grid_tag {
    int rows, columns;
    char grid[][1]; /* actually grid[rows][columns] */
} GRID;

and you can

assert(1*sizeof(int) == sizeof(ARRAY));
assert(2*sizeof(int) == sizeof(GRID));

Many people think C arrays are quirky. But they're also an elegant solution which allows the declaration of indefinitely complex arrays. It is known as the "K&R array equation". A good explanation can be found here.

Hope this helps.

Andreas Spindler
  • 7,568
  • 4
  • 43
  • 34
  • In your original code, you've declared grid[1][1] so the compiler believes the width of each row in the grid to be 1. So, accessing the cell at grid[x][y] presumably will not work. For example, grid[0][3] would seem to yield the same cell as grid[1][2] (and more generally *every* grid[x][y] where x+y=3). To make this work, would you not have to access cells by grid[0][x + y*cols]? – jarmod Jan 09 '16 at 21:08
0

This one does it, assuming a squared grid:

void print_grid(void* g, int size)
{
  char* my = (char*) g;
  int i, j;
  for (i=0; i<size; i++)
  {
    for (j=0; j<size; j++)            
       printf("%c ", my[i+j]);
    printf("\n");
  }
}

If you want to use non-squared grids, replace size with a rows and columns parameter and adjust counting: i<rows and j<columns.

eckes
  • 64,417
  • 29
  • 168
  • 201