20

Consider the following code (it came about as a result of this discussion):

#include <stdio.h>

void foo(int (*p)[]) {          // Argument has incomplete array type
    printf("%d\n", (*p)[1]);
    printf("%d\n", p[0][1]);    // Line 5
}

int main(void) {
    int a[] = { 5, 6, 7 };
    foo(&a);                    // Line 10
}

GCC 4.3.4 complains with the error message:

prog.c: In function ‘foo’:
prog.c:5: error: invalid use of array with unspecified bounds

Same error message in GCC 4.1.2, and seems to be invariant of -std=c99, -Wall, -Wextra.

So it's unhappy with the expression p[0], but it's happy with *p, even though these should (in theory) be equivalent. If I comment out line 5, the code compiles and does what I would "expect" (displays 6).

Presumably one of the following is true:

  1. My understanding of the C standard(s) is incorrect, and these expressions aren't equivalent.
  2. GCC has a bug.

I'd place my money on (1).

Question: Can anyone elaborate on this behaviour?

Clarification: I'm aware that this can be "solved" by specifying an array size in the function definition. That's not what I'm interested in.


For "bonus" points: Can anyone confirm that MSVC 2010 is in error when it rejects line 10 with the following message?

1><snip>\prog.c(10): warning C4048: different array subscripts : 'int (*)[]' and 'int (*)[3]'
4

4 回答 4

15

Section 6.5.2.1 of n1570, Array subscripting:

Constraints

One of the expressions shall have type ‘‘pointer to complete object type’’, the other expression shall have integer type, and the result has type ‘‘type’’.

So the standard forbids the expression p[0] if p is a pointer to an incomplete type. There is no such restriction for the indirection operator *.

In older versions/drafts of the standard, however, (n1256 and C99), the word "complete" is absent in that paragraph. Not being involved in any way in the standard procedure, I can only guess whether it's a breaking change or the correction of an omission. The behaviour of the compiler suggests the latter. That is reinforced by the fact that p[i] is per the standard identical to *(p + i) and the latter expression doesn't make sense for a pointer to an incomplete type, so for p[0] to work if p is a pointer to an incomplete type, an explicit special case would be needed.

于 2012-04-17T01:45:37.007 回答
5

My C is a bit rusty, but my reading is that when you have an int (*p)[] this:

(*p)[n]

Says "dereference p to get an array of ints, then take the nth one". Which seems naturally to be well defined. Whereas this:

p[n][m]

Says "take the nth array in p, then take the mth element of that array". Which doesn't seem well-defined at all; you have to know how big the arrays are to find where the nth one starts.

This could work for the specific special case where n = 0, because the 0th array is easy to find regardless of how big the arrays are. You've simply found that GCC isn't recognising this special case. I don't know the language spec in detail, so I don't know whether that's a "bug" or not, but my personal tastes in language design are that p[n][m] should either work or not, not that it should work when n is statically known to be 0 and not otherwise.

Is *p <===> p[0] really a definitive rule from the language specification, or just an observation? I don't think of dereferencing and indexing-by-zero as the same operation when I'm programming.

于 2012-04-17T01:55:13.083 回答
4

For your "bonus points" question (you probably should have asked this as a separate question), MSVC10 is in error. Note that MSVC only implements C89, so I have used that standard.

For the function call, C89 §3.3.2.2 tells us:

Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter.

The constraints for assignment are in C89 §3.3.16:

One of the following shall hold: ... both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

So we can assign two pointers (and thus call a function with a pointer parameter using a pointer argument) if the two pointers point to compatible types.

The compatibility of various array types is defined in C89 §3.5.4.2:

For two array types to be compatible, both shall have compatible element types, and if both size specifiers are present, they shall have the same value.

For the two array types int [] and int [3] this condition clearly holds. Therefore the function call is legal.

于 2012-04-17T04:26:23.363 回答
0
void foo(int (*p)[])
{
    printf("%d\n", (*p)[1]);
    printf("%d\n", p[0][1]);    // Line 5
}

Here, p is a pointer to an array of an unspecified number of ints. *p accesses that array, so (*p)[1] is the 2nd element in the array.

p[n] adds p and n times the size of the pointed-to array, which is unknown. Even before considering the [1], it's broken. It's true that zero times anything is still 0, but the compiler's obviously checking the validity of all the terms without short-circuiting as soon as it sees zero. So...

So it's unhappy with the expression p[0], but it's happy with *p, even though these should (in theory) be equivalent.

As explained, they're clearly not equivalent... think of p[0] as p + 0 * sizeof *p and it's obvious why....

For "bonus" points: Can anyone confirm that MSVC 2010 is in error when it rejects line 10 with the following message? 1>\prog.c(10): warning C4048: different array subscripts : 'int ()[]' and 'int ()[3]'

Visual C++ (and other compilers) are free to warn about things that they think aren't good practice, things that have been found empirically to be often erroneous, or things the compiler writers just had an irrational distrust of, even if they're entirely legal re the Standard.... Examples that may be familiar include "comparing signed and unsigned" and "assignment within a conditional (suggest surrounding with extra parentheses)"

于 2012-04-17T04:43:28.523 回答