Semantics for dereferencing incomplete types?

Take the following two testcases:

void
foo (void)
{
  struct b;
  struct b* x = 0;
  struct b* y = &*x;
}

void foo2 (void)
{
  typedef int (*arrayptr);
  arrayptr x = 0;
  arrayptr y = &*x;
}

Currently, clang rejects both of these. Checking with some of the
other c compilers I have access to, the online comeau compiler,
tinycc, and lcc-win32 accept both, while gcc accepts the second but
not the first (which seems really strange, but according to some
diving into svn revision history, gcc's been doing that for at least
15 years...).

The place in the source where clang outputs the error,
Sema::CheckIndirectionOperand, justifies the error by pointing to C99
6.5.3.2p4, "If the operand points to a function, the result is a
function designator; if it points to an object, the result is an
lvalue designating the object.", and the comment concludes that this
means dereferencing an incomplete type is illegal.

My interpretation of the text in the C99 standard is that saying that
x "points to an object" doesn't actually mean that x has an object
type. Per the standard, an object is "region of data storage in the
execution environment, the contents of which can represent values,"
which is much different from an object type. Besides that, there is a
footnote stating that "&*E is equivalent to E", and the standard
earlier explicitly states that an lvalue can have an incomplete type.

Granted, the phrasing in the standard isn't especially well-chosen.
(It gets even worse with the definition of array subscripting, which
is defined to take a "pointer to object /type/", where /type/ is
italicized). That said, I can't see any good reason why either of the
above functions should be disallowed by the standard, since the
meaning is well-defined.

Here is another test:
void foo3 (void)
{
  void* x = 0;
  void* y = &*x;
}

I think this testcase is equivalent according to the standard, because
void is just another incomplete type which is defined to have no
members and is always incomplete. gcc, clang, and llc-win32 warn on
this construct, but all the compilers I tested accept it, which makes
it kind of weird to reject incomplete array or struct types.

-Eli

Eli Friedman wrote:-

The place in the source where clang outputs the error,
Sema::CheckIndirectionOperand, justifies the error by pointing to C99
6.5.3.2p4, "If the operand points to a function, the result is a
function designator; if it points to an object, the result is an
lvalue designating the object.", and the comment concludes that this
means dereferencing an incomplete type is illegal.

Certainly in

extern int x;

x is an object.

Neil.

Eli Friedman wrote:-

Here is another test:
void foo3 (void)
{
  void* x = 0;
  void* y = &*x;
}

Correct behaviour there depends on dialect:

neil@duron:~/src/c$ ~/src/cfe/cfe /tmp/bug.c --c90
"/tmp/bug.c", line 4: error: expression must be an lvalue or a function
  void* y = &*x;
             ^
"/tmp/bug.c", line 4: warning: variable "y" declared but not used
  void* y = &*x;
        ^

1 error found compiling "/tmp/bug.c".
neil@duron:~/src/c$ ~/src/cfe/cfe /tmp/bug.c --c99
"/tmp/bug.c", line 4: warning: variable "y" declared but not used
  void* y = &*x;
        ^

Neil.

Right... it's actually a completely different case from the first two
functions because void expressions aren't lvalues.

Wow, the C standard is really confusing.

-Eli

Attached is a patch which makes all of these testcases work correctly
(as far as I can tell). Essentially, this just removes the error for
a completely legal construct and fixes up the void* case to work per
C99.

-Eli

fixderef.txt (2.01 KB)

Eli Friedman wrote:-

> Correct behaviour there depends on dialect:
>
> neil@duron:~/src/c$ ~/src/cfe/cfe /tmp/bug.c --c90
> "/tmp/bug.c", line 4: error: expression must be an lvalue or a function
> void* y = &*x;
> ^
> "/tmp/bug.c", line 4: warning: variable "y" declared but not used
> void* y = &*x;
> ^
>
> 1 error found compiling "/tmp/bug.c".
> neil@duron:~/src/c$ ~/src/cfe/cfe /tmp/bug.c --c99
> "/tmp/bug.c", line 4: warning: variable "y" declared but not used
> void* y = &*x;
> ^

Right... it's actually a completely different case from the first two
functions because void expressions aren't lvalues.

Wow, the C standard is really confusing.

It's worse. qualified void is an lvalue, so qualifying the voids
in C90 should be accepted not rejected :slight_smile: So for example (excuse
my posting more output from my FE) in the following c is bad but
d is OK:

$ ~/src/cfe/cfe /tmp/bug.c --c90
"/tmp/bug.c", line 1: error: expression must be an lvalue or a function
void f(void) { void *a; const void *b; void *c = &*a; const void *d =
&*b; }
                                                  ^
"/tmp/bug.c", line 1: warning: variable "c" declared but not used
void f(void) { void *a; const void *b; void *c = &*a; const void *d =
&*b; }
                                             ^
"/tmp/bug.c", line 1: warning: variable "d" declared but not used
void f(void) { void *a; const void *b; void *c = &*a; const void *d =
&*b; }
                                                                  ^

1 error found compiling "/tmp/bug.c".

GCC doesn't get all this stuff right from what I remember; there
are outstanding PRs. It's not hard to get right though.

Neil.