arrayscpointerslanguage-lawyerfunction-parameter

Why can't I declare int n[][] but I can declare int (*n)[] as a parameter?


The C23 standard says(6.7.7.4 #7):

A declaration of a parameter as "array of type" shall be adjusted to "qualified pointer to type", where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation...

And (6.7.7.3 #1):

...The element type shall not be an incomplete or function type...

It turns out that I can't declare something like this

void function(int n[][]){...}

since an array cannot have elements with an incomplete type, but I can declare a

void function(int(*n)[]){...}.

But why? After all, paragraph 6.7.7.4 #7 says that: "A declaration of a parameter as an "array of type" shall be adjusted to qualified pointer to type" and it turns out that from int n[][] we get int (*n)[] and this is the same as int (*n)[].

Maybe this is because int n[][] and int(*p)[] are declarations?

Is there anything in the standard about this?

If so, please provide these quotes.


Solution

  • Why can't I declare int n[][] but I can declare int (*n)[]?

    Ultimately, because the spec says so. You have already quoted the provision (C23 6.7.7.3/1; a constraint) that says that the declared element type of an array must not be an incomplete type, such as int[]. This is on top of the rules of the formal grammar, which, by themselves, do not distinguish this case. But the formal grammar does allow incomplete types to be declared and used in some other contexts, including declaration of incomplete array types with complete element types, such as in int *n[], and pointers to incomplete array types, such as int (*n)[].

    You raise the automatic "adjustment" of array types for function parameters to corresponding pointer types, but before this can apply, there needs to be a valid parameter declaration to adjust. So when you ask,

    Maybe this is because int n[][] and int(*p)[] are declarations?

    Basically yes. More precisely, it's because function parameter declarations are declarations themselves, and subject to the rules for declaration, and no exception has been made for them in this area.

    Note well that parameter-type adjustment aligns with the automatic conversion of array values to pointer values in (most) expressions, including among the arguments in function-call expressions. That array values can be converted to corresponding pointer values must not be interpreted as arrays being pointers. They definitely are not. Generally speaking, an array of T has different size and different representation than a pointer to T.

    The point of the adjustment is to allow function parameters to be declared using the same form as arrays from which the corresponding pointer arguments are derived. Since you cannot have an object with declared type int[][], that declaration-matching purpose is not relevant to such types.


    Aside: consider what you could do with a hypothetical n declared as

    int n[][];
    

    You cannot define an object with that type, because it does not convey the amount of storage to reserve for the object.

    You cannot compute its size because, again, that information is not conveyed by this type.

    You cannot directly access any elements, not even element [0][0], because n[i] is defined in terms of the equivalent expression *((n) + (i)), and pointer + integer addition is defined only for pointers to complete types (which itself makes sense because it is defined in units of the pointed-to type).

    Among the very few things you could do with such an n would be to obtain its address via the & operator or to obtain a related pointer via array-to-pointer decay. The former doesn't get you much of anywhere useful. The latter not much more so -- you would need to additionally dereference the result (and rely on another level of array-to-pointer decay) in order to access any elements: (*n)[0]. For that, you might as well declare n as

    int (*n)[];
    

    Now the language does not need to forbid arrays with incomplete element type just because they would be redundant and mostly useless, but it doesn't lose very much by doing so, and it potentially makes the language easier to implement.