Issue 1120: Full expressions, declarators, and abstract declarators

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.