Authors: Jay Ghiron
Date: 2026-10-13
Submitted against: C23
Status: Open
Cross-references: 0439, 0495
This is related to DR 439 part D and DR
495. Those focused on expressions not belonging to any
full expression, but did not clarify how variably modified abstract
declarators in the declaration specifiers of a declaration interact
with variably modified declarators in the same declaration. All
following examples assume that the implementation does not support any
extended integer constant expressions that would change their
behavior. This issue focuses on typeof, but the same issues also
apply to atomic type specifiers.
A full expression is an expression that is not part of another expression, nor part of a declarator or abstract declarator. There is also an implicit full expression in which the non-constant size expressions for a variably modified type are evaluated; within that full expression, the evaluation of different size expressions are unsequenced with respect to one another. There is a sequence point between the evaluation of a full expression and the evaluation of the next full expression to be evaluated.
(C23 6.8.1 "General" paragraph 4.)
Consider the following:
#include<stdio.h>
struct S{int a[1];};
struct S f(){
return(struct S){};
}
int*p;
int g(int*q){
p=q;
return 1;
}
int h(){
printf("%i\n",p?*p:1);
return 1;
}
int main(){
typeof(char[g(f().a)])b[h()];
}
What should the behavior of this program be? If the calls to g and
h belong to different full expressions, then the behavior appears to
be undefined because the lifetime of the object with temporary
lifetime would have ended before the call to h had begun. If the
calls to g and h belong to the same full expression, then it
appears the result is unspecified: either zero or one can be printed.
Note that the nonnormative list of full expressions after the quoted
paragraph does not include a separate full expression for the call to
g, but also says that the full declarator for b is a full
expression which would mean that the call to g is not part of it.
This should also be considered in the context of typeof with an
expression:
#include<stdio.h>
int main(){
typeof(putchar('A'),(char(*)[putchar('B')||1])0)c[putchar('C')||1];
putchar('\n');
}
This appears to be well defined since the expression in the typeof
satisfies the definition of a full expression. However,
implementations do not all seem to agree with this. GCC prints ABC,
while Clang prints CAB. TCC prints BC, though that is obviously
nonconforming. Parameter declarations, including unnamed parameters
should also be considered. Note that in type names that are inside of
expressions full expressions would not be created so there does not
appear to be any sequencing guarantees. If the intent in the first
example is that the calls to g and h belong to different full
expressions, it should be considered if nested typeof operators can
create further full expressions. Lastly, any correction should
consider that in a declaration the type specified in the declaration
specifiers is visible from all declarators in the same declaration
except the first and all initializers in the same declaration.