3

I am trying to pass a two-dimensional array to a function. I am not having trouble to pass it to the function. But I am having trouble in understanding the logic behind this. The functions and main definition goes like this:

// Function to print the two-dimensional array
void print(int x, int y, int a[x][y]){
    printf("\n");
    int i, j;
    for(i = 0; i < x; i++){
        for(j = 0; j < y; j++)
            printf("%d     ", a[i][j]);
        printf("\n");
    }
}

// Function to initialize the two-dimensional array
void init_2d(int *a, int x, int y){
    int i, j;
    for(i = 0; i < x; i++){
        for(j = 0; j < y; j++){
            a[i*y + j] = i + j;
        }
        printf("\n");
    }
}

int main(){
    int m = 2, n = 3;
    int a[m][n];  // a two dimensional whose size has been defined using m and n
    init_2d(a, m, n);
    print(m, n, a);
}

Ironically everything is working fine. And that is my problem as I am not able to digest the logic of it.

The main problems are:

  1. What I read in books was that the size of the two-dimensional arrays should be defined using constants or symbolic constants. In my main I am defining 2-D array using variables m and n and yet it works fine. Why?
  2. I was also told to pass the two-dimensional array by decaying it into a single dimensional array (by defining it as a pointer to int in function) i.e. the way I have done it in function init_2d. But in the print function I am doing it using a two dimensional array whose size has been defined using variables x and y. Is it fine to do so?
  3. Can a two-dimensional array be traversed using a pointer to a pointer as well?

Can anybody suggest me a good read on this topic which can clear all my concepts?

I am using codeblocks to compile my code and the compiler is GNU GCC Compiler.

icedwater
  • 4,701
  • 3
  • 35
  • 50
Nishant
  • 409
  • 6
  • 16
  • 2 is odd - I'm surprised it compiled! – John3136 Jun 19 '13 at 01:39
  • @John3136 C99 supports forward parameter declarations. –  Jun 19 '13 at 01:42
  • @Armin: What's "forward parameter declaration" and how is it relevant to John3136's comment? – AnT stands with Russia Jun 19 '13 at 04:20
  • @AndreyT What's "forward parameter declaration" this: `void print(int x, int y, int a[x][y])` and is connected to VLA's. http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Variable-Length.html I got the keyword from somewhere else though. –  Jun 19 '13 at 10:24

6 Answers6

7
  1. Yes and no. What you read in books is true for the older, original version of C language specification - C89/90. Since C99 version of C language compilers support so called Variable Length Arrays (VLAs), whose size can be specified by run-time values. You inadvertently used that C99-specific feature of the language, which is apparently supported by your compiler in its default mode. If you ask your compiler to switch to the strict C89/90 mode your code will fail to compile specifically because you used non-constants to specify array sizes.

    Note that even in C99 VLAs are supported only in certain contexts, like local arrays and function parameter declarations. You will not be able to declare a static VLA or a VLA that is a member of a struct type. In those contexts you are still required to use constant array sizes even in C99.

  2. Whoever told you to decay 2D arrays to 1D arrays was very wrong. The way you use init_2d in your program is invalid: you are not allowed to "reinterpret" a 2D array as a 1D array by yourself. Any C compiler will immediately complain about your

    init_2d(a, m, n);
    

    call, since this call attempts to pass 2D int array argument for an int * parameter. This is illegal. To silence the compiler you'd have to do

    init_2d((int *) a, m, n);
    

    It is quite possible that your init_2d will "work as expected", but still there's no good reason to use hacks like that.

    What can really be done here is decaying 2D array to a pointer to a 1D array. Believe it or not, this is exactly what you already did in your print declaration, even if it is not immediately noticeable. Your

    void print(int x, int y, int a[x][y])
    

    declaration is actually equivalent to

    void print(int x, int y, int (*a)[y])
    

    declaration. I.e. a is actually a pointer to type int [y] - a pointer to a 1D int array of size y. The above two declarations are just two superficially different ways to say the same thing.

    Note also that are not required to pass 2D arrays to functions by "decaying them" to anything. You can pass a 2D array by pointer to the entire 2D array, as in

    void print(int x, int y, int (*a)[x][y])
    

    In this case to pass the array you have to use & operator

    print(m, n, &a);
    

    and to access the array inside the function you have to use * operator

    printf("%d     ", (*a)[i][j]);
    
  3. No, in your case it can't be. A built-in 2D array cannot be traversed by pointer-to-pointer. BTW, as you already figured out in your init_2d implementation (which "works", even though it is illegal), a 2D array is internally implemented as a 1D array with automatic 2D-to-1D index recalculation. Pointer-to-pointer won't help you to traverse such "flat" linear data.

    Pointer-to-pointer types (or, more generally, multi-level pointer types) are often used when working with a completely different kind of multi-dimensional arrays: when an N-dimensional array is implemented as an array of pointers to independently allocated (N-1)-dimensional arrays. This kind of multi-dimensional array is also used quite often in C programs, but it has to be implemented and managed manually (you can find quite a few examples here on SO). Meanwhile, as I said above, built-in language arrays are implemented in a completely different fashion and they cannot be traversed by multi-level pointers.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Your answer was very helpful. Thank You :) I have some doubts regarding your answer : I can understand that a data structure which is declared like a[m][n] cannot be traversed using two dimensional arrays. But say I declare a pointer to a pointer (**a) and allocate proper space to it using malloc. Then can I use it like a two-dimensional array (a[i][j]). If it works then does it also work with the older versions of C. – Nishant Jun 19 '13 at 13:24
  • Another Doubt : If I declare a[m][n] then isn't 'a' a pointer to an array having m x n size. Then why can't a two-dimensional array be passed as a pointer as I did it it in 'init_2d' funtion. – Nishant Jun 19 '13 at 13:25
  • @Nishant: As I said above, it is possible to manually implement a special kind of 2D array through a `(**a)` pointer. However, it is not possible to simply allocate a "flat" block of memory for such an array. An array implemented through `(**a)` pointer has to have two-tiered structure. This structure has to be built manually. And it will be completely different from and incompatible to a built-in 2D array. See here for example: http://stackoverflow.com/questions/2842135/c-programming-malloc-for-a-2d-array-using-pointer-to-pointer – AnT stands with Russia Jun 19 '13 at 16:49
4
  1. VLAs (variable length arrays) exist in some versions of C. VLAs allow for allocating a fixed-length array on the stack using runtime-assigned variables, versus on the heap via malloc() or similar function.

  2. I have never seen the array declaration syntax used in print() before, so I can't answer that. Typically one would leave the brackets empty, or use a decayed pointer.

  3. yes.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
4
  1. ISO C90 forbids variable length array, but your compiler is newer, so it is okay. It will fail if you compile with -ansi -pedantic -Wall flags
  2. This will also fail if you compile with -ansi -pedantic -Wall flags (ISO C90 doesn't like it either). Your newer compiler is fine with it though.
  3. Yes, you can use a pointer to a pointer to traverse the 2d array (In fact, char** argv parameter for main is an example)

It looks like the books you are reading conform to the C90 standard. Newer versions of gcc are okay though.

jh314
  • 27,144
  • 16
  • 62
  • 82
3
  1. VLA.
  2. init_2d takes a int *, but you have passed it a int (*)[n]. However, since a is an array of an array, the address of a[0] is the same as the address of a[0][0], which is why init_2d "works". Your compiler should have warned you about a type incompatibility when you passed a to init_2d. If it did not, you should complain to your compiler vendor.

    print uses VLA for the third parameter.

  3. If you mean "Can I traverse a with an int **, the answer is "not in a straightforward manner". The reason is that if you have int **p, you can only realistically traverse a if p is initialized like this:

    int a[n][m];
    int *ap = &a[0][0];
    int **p = &ap;
    

    But, then you are just traversing a via ap by dereferencing p. A pointer to a pointer is usually used to represent an array of pointers.

    int *x[10];
    int **p = x;
    

    But, your a is not an array of pointers.

jxh
  • 69,070
  • 8
  • 110
  • 193
1

Memory allocation in C arrays is contiguous, that is why expression a[i][j] (init_2d) can be assimilated to a[i*y + j] (print).

a as int** (2-dim : N x M) can be seen as a int* one-dimensional array as well (size : N*M)...

See good others answers for 1. 2. and 3.

See : Row-major order (C-style), so 2. is fine.

Note : Apparently, what I'm saying here is only true with recent versions of the C programming language and/or some specific context and is absolutely not generalizable. So better read the several other answers first.

Community
  • 1
  • 1
Gauthier Boaglio
  • 10,054
  • 5
  • 48
  • 85
0

1) m and n in your main have already a value so your array declaration is just fine.They are used just as numeric constants. 2)An array is a pointer. So

void foo(int array[]);
void foo(int* array);

are exactly the same.

An array int a[2][2] is a 1-dimensional array with 2 pointers to int and those 2 pointers point to 2 integers. So with right playing around your first pointer(in your case a) you can access those integers.

3) look up to wikipedia. There is a good algorithm to understand better.