2

I have a function like this:

void myfunc(int** arr, int n) {
  int i, j;
  for(i=0; i<n; ++i) {
    for(j=0; j<n; ++j) {
      printf("%d,", *(arr + i*n + j) );  // Print numbers with commas
    }
    printf("\n");                        // Just breakline
  }
}

in other function I have an two-dimensional array like this:

int main() {
  int seqs[8][8] = {
    {0, 32, 36, 52, 48, 16, 20, 4},
    {0, 16, 20, 52, 48, 32, 36, 4},
    {0, 32, 36, 44, 40, 8, 12, 4},
    {0, 8, 12, 44, 40, 32, 36, 4},
    {0, 32, 36, 38, 34, 2, 6, 4},
    {0, 2, 6, 38, 34, 32, 36, 4},
    {0, 32, 36, 37, 33, 1, 5, 4},
    {0, 1, 5, 37, 33, 32, 36, 4}
  };

  // Call to myfunc
  myfunc(seqs, 8);  // This is the idea
}

But compiler throw me this error:

lab.c: In function 'main':
lab.c:75:5: warning: passing argument 1 of 'myfunc' from incompatible pointer type [enabled by default]
lab.c:4:6: note: expected 'int **' but argument is of type 'int (*)[8]'

What is the right way to pass this array (seqs) to function (myfunc)?

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
Israel
  • 3,252
  • 4
  • 36
  • 54
  • http://www-ee.eng.hawaii.edu/~dyun/ee160/Book/chap7/section2.1.2.html – Jeyaram Feb 17 '14 at 06:44
  • It should look like `void myfunc(int arr[][8], int n) {`. –  Feb 17 '14 at 06:44
  • From the looks of the code in the function, you actually need an `int *` rather than an `int **` for your `arr` argument. Then just pass in the address of the first element (`&seqs[0][0]`). – Dmitri Feb 17 '14 at 07:01
  • You're passing a pointer to a pointer, yet you're only dereference that pointer once (`*(n + ...)`). Why not exploit the fact that `ptr[offset]` is equivalent to `*(ptr+offset)`? So you can simply do `printf("%d", arr[i][j]);` Oh, and `main` should return an int... add `return 0;` at the end – Elias Van Ootegem Feb 17 '14 at 07:06
  • You'd mentioned in a comment on one of the answers that you cannot change the function signature for `myfunc()`... why is that, exactly? – Dmitri Feb 18 '14 at 02:02

5 Answers5

5

In C99 or C11, you would do it like this:

void myfunc(int n, int arr[n][n])
{
    for (int i = 0; i < n; ++i)
    {
        for (int j = 0; j < n; ++j)
            printf("%d,", arr[i][j]);
        printf("\n");
    }
}

Note that the size precedes, not follows, the array. This function will work correctly with:

int main(void)
{
    int seqs[8][8] =
    {
        { 0, 32, 36, 52, 48, 16, 20, 4 },
        { 0, 16, 20, 52, 48, 32, 36, 4 },
        { 0, 32, 36, 44, 40,  8, 12, 4 },
        { 0,  8, 12, 44, 40, 32, 36, 4 },
        { 0, 32, 36, 38, 34,  2,  6, 4 },
        { 0,  2,  6, 38, 34, 32, 36, 4 },
        { 0, 32, 36, 37, 33,  1,  5, 4 },
        { 0,  1,  5, 37, 33, 32, 36, 4 },
    };
    myfunc(8, seqs);

    int matrix3x3[3][3] = { { 1, 2, 3 }, { 2, 4, 6 }, { 3, 6, 9 } };
    myfunc(3, matrix3x3);
}

I was asked:

Your example does look much better indeed, but is it well-defined? Is n really guaranteed to be evaluated before int arr[n][n]? Wouldn't the order of evaluation of function parameters be unspecified behavior?

The old standard (ISO/IEC 9899:1999) says in §6.7.5.2*Array declarators*:

¶5 If the size is an expression that is not an integer constant expression: if it occurs in a declaration at function prototype scope, it is treated as if it were replaced by *; otherwise, each time it is evaluated it shall have a value greater than zero. The size of each instance of a variable length array type does not change during its lifetime. Where a size expression is part of the operand of a sizeof operator and changing the value of the size expression would not affect the result of the operator, it is unspecified whether or not the size expression is evaluated.

And it gives an example (it is non-normative text because it is an example, but strongly indicative of what is expected):

EXAMPLE 4 All declarations of variably modified (VM) types have to be at either block scope or function prototype scope. Array objects declared with the static or extern storage-class specifier cannot have a variable length array (VLA) type. However, an object declared with the static storage class specifier can have a VM type (that is, a pointer to a VLA type). Finally, all identifiers declared with a VM type have to be ordinary identifiers and cannot, therefore, be members of structures or unions.

extern int n;
int A[n];                       // invalid: file scope VLA
extern int (*p2)[n];            // invalid: file scope VM
int B[100];                     // valid: file scope but not VM
void fvla(int m, int C[m][m]);  // valid: VLA with prototype scope
void fvla(int m, int C[m][m])   // valid: adjusted to auto pointer to VLA
{
    typedef int VLA[m][m];      // valid: block scope typedef VLA
    struct tag {
        int (*y)[n];            // invalid: y not ordinary identifier
        int z[n];               // invalid: z not ordinary identifier
    };
    int D[m];                   // valid: auto VLA
    static int E[m];            // invalid: static block scope VLA
    extern int F[m];            // invalid: F has linkage and is VLA
    int (*s)[m];                // valid: auto pointer to VLA
    extern int (*r)[m];         // invalid: r has linkage and points to VLA
    static int (*q)[m] = &B;    // valid: q is a static block pointer to VLA
}

There are other examples showing variably modified function parameters.

Also, in §6.9.10 Function definitions, it says:

¶10 On entry to the function, the size expressions of each variably modified parameter are evaluated and the value of each argument expression is converted to the type of the corresponding parameter as if by assignment. (Array expressions and function designators as arguments were converted to pointers before the call.)

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 2
    Uhms, this was your answer number 2^13, pretty poetic :) – fedorqui Feb 17 '14 at 13:23
  • The last comma in the initialization of `seq` does not cause any warning. I don't know if it should but it reminds me of Python's list, tuple and dict types where trailing comma is acceptable. – ajay Feb 17 '14 at 20:31
  • 1
    @ajay: C has allowed a trailing comma in initializers since before K&R first edition (1978 or thereabouts). C89 didn't allow a trailing comma when defining an enumeration, but C99 added a rule to explicitly allow it. It makes it much easier to generate C initializers, and adding a trailing initializer means that modifications are less likely to fail (because you didn't add the extra comma when you added the extra line of data). – Jonathan Leffler Feb 17 '14 at 22:30
2

Arrays and pointers are not the same. Similarly, two-dimensional array and pointer to pointer are not the same thing. The type of seqs mismatches function argument, you can modify the signature of myfunc to:

void myfunc(int arr[][8], int n) 

or, you can modify seqs to a real pointer to pointer.

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
  • How can you quiet the warning and still accept arrays of any size? That's the real issue, I think – salezica Feb 17 '14 at 06:45
  • 1
    @uʍopǝpısdn You are right, the OP has the parameter `n` for a reason. A real pointer to pointer is one option. – Yu Hao Feb 17 '14 at 06:51
  • @YuHao, I cannot change `myfunc` signature, I can change `seqs` from array to pointer, but is there a way to pass `seqs` as `int[][]` or I must change it? – Israel Feb 17 '14 at 07:16
  • @Israel When passed as function argument, only the first dimension can be free, the other dimension must be specified, so you can't declare a function with `int [][]` as argument. – Yu Hao Feb 17 '14 at 07:21
1

You're declaring the int** as an array with a static size, so its signature is int *[8]. You can get rid of the compiler error by casting the array when you call myfunc.

myfunc((int **)seqs, 8);

In an actual program such an array would most likely be generated dynamically, and you wouldn't have to do this.

Maroun
  • 94,125
  • 30
  • 188
  • 241
OregonTrail
  • 8,594
  • 7
  • 43
  • 58
  • 1
    If you allocate a dynamic 2D array [properly](http://stackoverflow.com/questions/12462615/how-do-i-correctly-set-up-access-and-free-a-multidimensional-array-in-c), you will have the same problem as here. If you allocate it as a pointer-based lookup table segmented all over the heap, yeah then you won't need the cast. – Lundin Feb 17 '14 at 07:12
  • Good to know, I didn't know `sizeof` handled multi-dimensional arrays like that. Looks like it may even go back to C90. – OregonTrail Feb 17 '14 at 07:15
  • Nope, unfortunately it doesn't (see the comments to the accepted answer), because you can't declare an array pointer a with a variable length in C90, it has to be a compile-time constant. – Lundin Feb 17 '14 at 07:35
  • This is plain wrong. You can't typecast `seqs` to `int **` type and make it work. – ajay Feb 17 '14 at 18:20
0

The way you're accessing the value from the array in your function, would give you the right element with a simple int * rather than an int **. With *(arr + i*n + j). The i*n gets you to the right row, and the j gets you the column. So change your function header line to:

void myfunc(int* arr, int n) {

...(with one '*'), and pass the address of the first element rather than the bare array name, like this:

myfunc(&seqs[0][0], 8);

..or the first row:

myfunc(seqs[0], 8);
Dmitri
  • 9,175
  • 2
  • 27
  • 34
0

When you define a 2-dimensional array as

int seqs[8][8];

the type of seqs is array of 8 elements where each element is of type array of 8 integers. When you pass seqs to myfunc, seqs is implicitly converted to a pointer to its first element (which is what happens when you pass an array to a function), i.e., to type * element. element is of type int [8]. Hence seqs is implicitly converted to type int *[8] - a pointer to an array of 8 integers.

In your function myfunc, the type of the parameter arr is int ** clearly a different type than what you pass it when you call it in main. They are different types and have different pointer arithmetic. The compiler complains as a result.

You should change the type of arr to int *[8].

void myfunc(int arr[][8], int n);

arr is not an array here but a pointer to an array of 8 integers and you might as well declare myfunc as

void myfunc(int *arr[8], int n);

They are exactly the same.

Edit

Regarding your comment here, you cannot do the following and expect things to work correctly.

// in main
myfunc((int **)seqs, 8);

// the above call is essentially doing this 
int **arr = (int **)seqs;

seqs and arr are incompatible types and typecasting seqs suppresses the warning emitted otherwise by the compiler which is worse because typecasting doesn't correct things, and make it seem as if things are alright.

Let us see why. (After the function call.)

seqs (type: array of 8 int[8] type; value: address of the location seqs[0][0])
*seqs (type: int[8] type, value: address of the location seqs[0][0])

arr (type: pointer to a pointer to an int; value: address of the location seqs[0][0])
*arr (type: pointer to an int; value: seqs[0][0])
**arr (type: int; value: value at the address seqs[0][0])

The above assertions can be checked with the assert macro. So we see that when we do **arr what we are actually doing is treating the value seqs[0][0] as a memory address and trying to access the value at that location. This is obviously wrong! It can lead to undefined behaviour or most likely segmentation fault.

There's no way to make the value of seqs (value it evaluates to in the initialization of arr) act like a int ** even by typecasting. This also shows that we should be careful with typecasting values and shouldn't do it unless we know and are sure of what we are doing.

Therefore, you must change your myfunc function signature to do what you want.

Community
  • 1
  • 1
ajay
  • 9,402
  • 8
  • 44
  • 71
  • Regarding the edit: casting to `int **` and dereferencing twice with `*(*(arr + i*n) + j)` won't work, since the address in `arr` will still be the start of the array data -- not the address of a pointer to it. – Dmitri Feb 17 '14 at 08:28
  • ...and that address doesn't contain pointers to the rows, but the rows themselves. – Dmitri Feb 17 '14 at 08:54