1

If I have a multidimensional array like this:

int arr2d[2][3] = {{0,1,2}, {10,11,12}};

I can pass it to a function like this:

void foobar(int arg[][3])

This is not a call by value, this is call by reference, so just an pointer to the start address, but the compiler still knows it is a 2D array and I'm able to access it like one in the function.

Now how does the same work in a struct?

typedef struct {
  int arr2d[][3];
} Foobar_t

First this gives me: error: flexible array member in otherwise empty struct. I can fix this by doing so:

typedef struct {
  int dummy;
  int arr2d[][3];
} Foobar_t

It will compile without errors or warnings. But when I try to use it like Foobar_t foobar = {1337, arr2d} I get some warnings:

missing braces around initializer
initialization makes integer from pointer without a cast

And when accessing it: subscripted value is neither array nor pointer nor vector.

One dimensional arrays can easily be treated as pointers. But for multi dimensional arrays the compiler needs to know the size of the different dimensions to calculate the offsets correctly. Is there a way without cast (int (*)[3]) and why does the syntax differ from the function parameter?

So this is the work-around I want to avoid:

#include <stdio.h>

static int testArr[2][3] = {{0,1,2},{10,11,12}};

typedef struct {
  int *arr2d;
} Foobar_t;


int main( int argc, char** argv ) {
  Foobar_t foobar = {(int*)testArr};
  int (*arr2d)[3] = (int (*)[3]) foobar.arr2d;
  printf("testStruct_0_0: %d\n", arr2d[0][0]);
  printf("testStruct_1_0: %d\n", arr2d[1][0]);

  return 1337;
}

Edit:

Some comments suggest that reference is not the correct word. Of course in the C language this is implemented by a pointer. So the TLDR of this questions is: How does the syntax for a pointer type to a multi dimensional array look like.

The answer can already be seen in my work-around code. So that is all, move along, nothing to see here ;) Nevertheless thanks for the replies.

4
  • There is no reference in C Commented Sep 3, 2017 at 17:59
  • 1
    You can't have incomplete types in a structure except as the FAM at the end of a structure with at least one other member (hence your dummy member). And when you have a FAM, you have to dynamically allocate the structure with the correct amount of memory. Commented Sep 3, 2017 at 18:01
  • In your workaround, your line Foobar_t foobar = {(int*)testArr}; could be written more cleanly as Foobar_t foobar = { &testArr[0][0] }; — no cast necessary. The second cast is not so easily avoided. Commented Sep 3, 2017 at 18:05
  • @ Filip Kočica: If there is no reference in C, then what is the dereference operator * used for? Commented Sep 3, 2017 at 20:52

3 Answers 3

1

There is no "call by reference" in the C language. Function arguments are always passed by value. Arrays do appear special, since an array decays to a pointer to its first element in most expressions (including function calls). This means that when an array is used as an argument in a function call, a pointer to the first element is passed to the function instead of an array; but it is the value of this pointer which is passed.

In function declarators, array types are adjusted to pointers to appropriate types. This is specific to the semantics of function declarators. Thus, a function declaration like:

void foobar(int arg[][3]);

is adjusted to take a pointer to an array of three ints as an argument:

void foobar(int (*arg)[3]);

In general, a type expression such as int arg[][3] is an incomplete type, since it is impossible to know the size of the array arg[][] without more information.

Structures in C do not allow member types to be specified with incomplete types (with one exception), since there is no way to know the size of the struct without this information. Further, struct specifiers do not make the same adjustment to array types that function declarators do, since structs may actually include array members.

The exception to the incomplete type rule in structs is with flexible array members. The last member of a struct with at least two named members may have an incomplete array type.

The simple solution to the problem in the question is to change the specifier for the struct to use a pointer to an array. Note that here the member .arr2d is not an array, but a pointer to an array of three ints:

typedef struct {
  int (*arr2d)[3];
} Foobar_t;
Sign up to request clarification or add additional context in comments.

8 Comments

Call-by-reference means that data is given by a reference: here the address, that can be dereferenced by the * operator (or implicit with the -> operator for some types).
Thanks for the answer. int (*arr2d)[3] was what I was searching for. I noticed it the moment I posted the question, because in my workaround I was using that exact type...
@cubei-- I saw that you had found the solution, but not the reason that int arr2d[][3] would work in function declarators but not struct specifiers; that was the main thrust of my answer. Also, C does not have references, but pointers. Some languages use references, but in C it is always a value that is passed to a function (whether an int, or a pointer to an int, for example); the distinction is important.
@cubei-- note that arrays are not passed by reference, because there is no such thing in the C language. Arrays decay to pointers to their first elements in function calls, and this pointer is passed by value. This is the only way that arguments are ever passed to functions in C; it can be thought of as simulated call by reference for arrays (and it is sometimes misleadingly said that arrays are passed by reference in C: they are not).
So what would be a reference that is not passed by value? Does c++ has something like that?
|
0

You could try using either int **arr2d, which would allow you to access it via arr2d[x][y] or simply convert it to a one-dimensional array like int *arr2d = malloc(2*3*sizeof(int));. That way, you'd need to access values like this: arr2d[x*m + y];, where x and y are the same of the previous example, while m is the size of a row.

I'd also suggest you to store both row number and column number of your 2-dimensional array into the struct.

3 Comments

If the type is int** and accessed via arr2d[x][y], then how does the compiler know that y has an offset of one int and x an offset of 3 ints?
It simply doesn't know any of this; infact you should be passing extra arguments to "manually" check that you wouldn't overrun any size. Remember though, while using int** variables, you should manually initialize every single one, like this: int **arr2d; //n = # of rows, m = # of columns --> allocate n*m "places" arr2d = (int **)malloc(n * m * sizeof(int)); //for each row i, allocate m "places" for(int i = 0 ; i < n; i++){ arr2d[i] = (int *)malloc(m * sizeof(int)); }
If it doesn't know, then an access via arr2d[x][y] is not possible without casting as stated above. The malloc stuff doesn't relate to the questen. Because it was stated that the array is already available and should just be referred to.
0

Turns out just looking at the work-around showed me the solution:

typedef struct {
  int (*arr2d)[3];
} Foobar_t;

This is the correct type for a pointer to a 2D array. type (*name)[n] also works for function parameters.

So why is the other syntax type name[][n] still valid for function parameters? Probably the conflicts with the flexible array feature of structs keep it from working there.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.