Aren't a[][] and (*a)[] equivalent as function parameters?

ε祈祈猫儿з 提交于 2019-12-01 15:23:40
AnT

No, they are not equivalent as function parameters. They are not equivalent in exactly the same way as parameter declarations in foo and bar

struct S;
void foo(struct S* s); // OK
void bar(struct S a[]); // ERROR: incomplete type is not allowed

are not equivalent.

C does not allow incomplete types as array elements (see C 1999 6.7.5.2/1: "[...] The element type shall not be an incomplete or function type. [...]") and this restriction applies to array parameter declarations the same way as it applies to any other array declarations. Even though parameters of array type will be later implicitly adjusted to pointer type, C simply provides no special treatment for array declarations in function parameter lists. In other words, array parameter declarations are checked for validity before the aforementioned adjustment.

Your int a[][] is the same thing: an attempt to declare an array with elements of type int [], which is an incomplete type. Meanwhile, int (*a)[] is perfectly legal - there's nothing unusual about pointers to incomplete types.

As a side note, C++ "fixed" this issue, allowing arrays of incomplete type in parameter declarations. However, the original C++ still prohibits int a[][] parameters, int (&a)[] parameters and even int (*a)[] parameters. This was supposedly fixed/allowed later in C++17 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393)

An incomplete type is allowed in contexts where the size doesn't need to be known.

With this declaration:

int a[][]

It is invalid even as a function parameter because the size of one array dimension is needed to know how to perform pointer arithmetic on the second dimension.

This however is valid:

int (*a)[];

Because the size of the array doesn't need to be known in order to use a pointer to it.

Section 6.2.7 of the C standard gives an example of a declaration like this:

5 EXAMPLE Given the following two file scope declarations:

int f(int (*)(), double (*)[3]);
int f(int (*)(char *), double (*)[]);

The resulting composite type for the function is:

int f(int (*)(char *), double (*)[3]);

This example shows a declaration of type double (*)[3] that is compatible with a declaration of type double (*)[]

You can't however directly use this like a 2D array because of the missing size. Here are some examples to illustrate. If you attempt to do this:

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

The compiler (as expected) tells you this:

error: invalid use of array with unspecified bounds
         printf("a[%d][%d]=%d\n",i,j,a[i][j]);
         ^

You can get around this by taking advantage of the fact that an array, even of indeterminate size, decays to a pointer in most contexts:

#include <stdio.h>

void foo(int n, int (*a)[])
{
    int i,j;
    for (i=0;i<n;i++) {
        // dereference "a", type is int[], which decays to int *
        // now manually add "n" ints times the row
        int *b = *a + (n*i);
        for (j=0;j<n;j++) {
            printf("a[%d][%d]=%d\n",i,j,b[j]);
        }
    }
}

int main()
{
    int a[2][2] = { {4,5},{6,7} };
    foo(2,a);

    return 0;
}

This compiles clean with the following output:

a[0][0]=4
a[0][1]=5
a[1][0]=6
a[1][1]=7

Even outside of a function, the int (*)[] syntax can be used:

#include <stdio.h>

int main()
{
    int a[2][2] = { {4,5},{6,7} };
    int i,j,n=2;
    int (*aa)[];
    // "a" decays from int[2][2] to int (*)[2], assigned to int (*)[]
    aa = a;
    for (i=0;i<n;i++) {
        int *b = *aa + (n*i);
        for (j=0;j<n;j++) {
            printf("a[%d][%d]=%d\n",i,j,b[j]);
        }
    }

    return 0;
}

EDIT

Having read through all the relevant parts of the standard, C11 6.7.6.2 and 6.7.6.3, I believe this is a compiler bug/non-conformance. it apparently boils down to the text that the committee sneaked into the middle of a paragraph concerning array delimiters. 6.7.6.2/1 emphasis mine:

In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.

Now this is of course very poorly written, basically it says

"peripheral feature of little interest, peripheral feature of little interest, peripheral feature of little interest, OUT OF THE BLUE HERE COMES SOME ARRAY ELEMENT TYPE SPECIFICATION NOT RELATED TO THE REST OF THIS PARAGRAPH, peripheral feature of little interest, peripheral feature of little interest,...."

So it is easy to misunderstand, fooled me.

Meaning that int a[][] is always incorrect no matter where it is declared, since an array cannot be an array of incomplete type.

However, my original answer below raises some valid concerns regarding whether array decay should be done before or after the compiler decides if the type is incomplete or not.


Given the specific case void foo(int n, int a[][]); only, this is a function declaration. It is not a definition.

C11 6.7.6.3/12

If the function declarator is not part of a definition of that function, parameters may have incomplete type

So first of all, parameters are allowed to have incomplete type in the function declaration. The standard is clear. Which is why code like this compiles just fine:

struct s; // incomplete type
void foo(int n, struct s a); // just fine, incomplete type is allowed in the declaration

Furthermore:

C11 6.7.6.3/4

After adjustment, the parameters in a parameter type list in a function declarator that is part of a definition of that function shall not have incomplete type.

After adjustment is very important here.

Meaning that after adjusting int a[][] to int (*a)[], the parameter shall not have incomplete type. It does not, it is a pointer to incomplete type, which is always allowed and perfectly fine.

The compiler is not allowed to first evaluate int a[][] as an incomplete array of incomplete arrays, and then later adjust it (if it found that the type was not incomplete). This would directly violate 6.7.6.3/4.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!