Kyle
Kyle

Reputation: 990

Is this behavior of clang standard compliant?

This is going to be a long, language lawyerish question, so I'd like to quickly state why I find it relevant. I am working on a project where strict standard compliance is crucial (writing a language that compiles to C). The example I am going to give seems like a standard violation on the part of clang, and so, if this is the case, I'd like to confirm it.

gcc says that a conditional with a pointer to a restrict qualified pointer can not co-inhabit a conditional statement with a void pointer. On the other hand, clang compiles such things fine. Here is an example program:

#include <stdlib.h>

int main(void){
   int* restrict* A = malloc(8);
   A ? A : malloc(8);
   return 0;
   }

For gcc, the options -std=c11 and -pedantic may be included or not in any combination, likewise for clang and the options -std=c11 and -Weverything. In any case, clang compiles with no errors, and gcc gives the following:

tem-2.c: In function ‘main’:
tem-2.c:7:2: error: invalid use of ‘restrict’
  A ? A : malloc(8);
  ^

The c11 standard says the following with regard to conditional statements, emphasis added:

6.5.15 Conditional operator

...

  1. One of the following shall hold for the second and third operands:

— both operands have arithmetic type;

— both operands have the same structure or union type;

— both operands have void type;

— both operands are pointers to qualified or unqualified versions of compatible types;

— one operand is a pointer and the other is a null pointer constant; or

one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void.

...

  1. If both the second and third operands are pointers or one is a null pointer constant and the other is a pointer, the result type is a pointer to a type qualified with all the type qualifiers of the types referenced by both operands. Furthermore, if both operands are pointers to compatible types or to differently qualified versions of compatible types, the result type is a pointer to an appropriately qualified version of the composite type; if one operand is a null pointer constant, the result has the type of the other operand; otherwise, one operand is a pointer to void or a qualified version of void, in which case the result type is a pointer to an appropriately qualified version of void.

...

The way I see it, the first bold portion above says that the two types can go together, and the second bold portion defines the result to be a pointer to a restrict qualified version of void. However, as the following states, this type can not exist, and so the expression is correctly identified as erroneous by gcc:

6.7.3 Type qualifiers, paragraph 2

Types other than pointer types whose referenced type is an object type shall not be restrict-qualified.

Now, the problem is that a "shall not" condition is violated by this example program, and so is required to produce an error, by the following:

5.1.1.3 Diagnostics, paragraph 1

A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined. Diagnostic messages need not be produced in other circumstances.

It seems clang is not standard compliant by treating an erroneous type silently. That makes me wonder what else clang does silently.

I am using gcc version 5.4.0 and clang version 3.8.0, on an x86-64 Ubuntu machine.

Upvotes: 9

Views: 333

Answers (2)

supercat
supercat

Reputation: 81159

While a compiler could satisfy all obligations surrounding restrict by ignoring the qualifier altogether, a compiler which wants to keep track of what it is or is not allowed to do needs to keep track of which pointers hold copies of restrict pointers. Given something like:

int *foo;
int *bar;
int wow(int *restrict p)
{
  foo = p;
  ...
  *p = 123;
  *foo = 456;
  *p++;
  *bar = 890;
  return *p;
}

since foo is derived from p, a compiler must allow for accesses made via foo to alias accesses via p. A compiler need not make such allowances for accesses made via bar, since that is known not to hold an address derived from p.

The rules surrounding restrict get murky in cases where a pointer may or may not be derived from another. A compiler would certainly be allowed to simply ignore a restrict qualifier in cases where it can't track all of the pointers derived from a pointer; I'm not sure if any such cases would invoke UB even if nothing ever modifies the storage identified by the pointer. If a syntactic construct is structurally guaranteed to invoke UB, having a compiler squawk may be more useful than having it act in an arbitrary fashion (though having a compiler simply ignore any restrict qualifiers it can't fully handle might be more useful yet).

Upvotes: 0

Jens Gustedt
Jens Gustedt

Reputation: 78923

Yes it looks like a bug.

Your question more briefly: can void be restrict qualified? Since void is clearly not a pointer type, the answer is no. Because this violates a constraint, the compiler should give a diagnostic.

I was able to trick clang to confess its sins by using a _Generic expression

puts(_Generic(A ? A : malloc(8), void* : "void*"));

and clang tells me

static.c:24:18: error: controlling expression type 'restrict void *' not compatible with any generic association type
     puts(_Generic(A ? A : malloc(8), void* : "void*"));

which shows that clang here really tries to match a nonsense type restrict void*.

Please file them a bug report.

Upvotes: 6

Related Questions