Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalence of p[0] and *p for incomplete array types

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]'
like image 758
Oliver Charlesworth Avatar asked Apr 17 '12 01:04

Oliver Charlesworth


3 Answers

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.

like image 81
Daniel Fischer Avatar answered Oct 18 '22 19:10

Daniel Fischer


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.

like image 27
Ben Avatar answered Oct 18 '22 18:10

Ben


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.

like image 4
caf Avatar answered Oct 18 '22 19:10

caf