Rationale for the C11 _Atomic specifier-vs-qualifier syntax irregularity? - c

There is a related "what" question here: C11 grammar ambiguity between _Atomic type specifier and qualifier
What I'm interested is why, since the C11 rationale hasn't been published yet, and this seems needlessly complex.
The grammar includes both of these (and resolves the ambiguity in favor of shifting the ( rather than reduce (which would otherwise be in lookahead if followed by a declarator or abstract-declarator)), reachable from declaration-specifiers or specifier-qualifier-list:
atomic-type-specifier: _Atomic ( type-name )
type-qualifier: _Atomic
This leads to the following code:
int i1;
int (i2); // valid, same as i1 - usually seen in the context of pointer-to-function or pointer-to-array
int _Atomic a1;
int _Atomic (a2); // invalid
_Atomic (int) a3; // valid, same as a1
Thoughts:
_Atomic must not modify an array or function. Barring redundant parentheses around a declarator, this means it would be valid to #define _Atomic(x) x _Atomic (if it were legal to #define keywords of course).
When it occurs as a qualifier, _Atomic is the same syntactical part as const, and C programmers are already used to putting const on the correct side, so it can't be for "ease of use".

The rationale is found in N1485:
The _Atomic keyword also can also be used in the form _Atomic(T),
where T is a type, as a type specifier equivalent to _Atomic T.
Thus, _Atomic(T) x, y; declares x and y with the same type, even
if T is a pointer type. This allows for trivial C++0x compatibility
with a C++ only _Atomic(T) macro definition as atomic<T>.

Related

Are both of these volatile qualifier usages redundant?

Consider
volatile int volatile * volatile vip; // (1)
and
volatile int volatile * volatile vipa[10]; // (2)
Both lines of code trigger -Wduplicate-decl-specifier (see rev 236142
and gcc7 release notes). I'd like to know if I can remove some volatile specifiers from the given code without changing the semantics of the code, and understand the reasons behind it, too.
Thus, the following questions:
a. In (1), do the 1st and 2nd volatile qualifiers both refer to int, thus being "duplicate" in gcc terms? (I'm looking at C99 6.7.3.4 here.)
b. In (2), does one of the volatile qualifiers refer to the type of the array, and not the int or the pointer itself, so that C99 6.7.3.8 holds:
If the specification of an array type includes any type qualifiers, the element type is so-qualified, not the array type.
or do the volatile specifiers in (2) only affect the int and the pointer type, and not the type of the array?
c. If the answer to b is negative, how do I declare a volatile array type that is described in C99 6.7.3.8? Is it the syntax described at https://en.cppreference.com/w/c/language/array (quote follows)?
qualifiers - any combination of const, restrict, or volatile qualifiers, only allowed in function parameter lists; this qualifies the pointer type to which this array parameter is transformed
Let's consider this a question about C99. If there are any differences in C11 in this regard, please make a note.
TL;DR:
In (1), do the 1st and 2nd volatile qualifiers both refer to int, thus being "duplicate" in gcc terms? (I'm looking at C99 6.7.3.4 here.)
yes, they both qualify the int and they're the duplicate.
In (2), does one of the volatile qualifiers refer to the type of the array, and not the int or the pointer itself, so that C99 6.7.3.8 holds:
C99 6.7.3.8 does not hold here. The qualifier already applies to the element type. It is possible to apply a qualifier to an array with a typedef, but that also qualifies the element type (see below)
c. If the answer to b is negative, how do I declare a volatile array type that is described in C99 6.7.3.8?
With a typedef for example.
The C standard explicitly allows qualifiers to occur more than once. C11 n1570 6.7.3p5:
If the same qualifier appears more than once in the same specifier-qualifier-list, either directly or via one or more typedefs, the behavior is the same as if it appeared only once.
i.e. what -Wduplicate-decl-specifier is not an error as such, but such code verbatim is suspicious - should it be volatile int *volatile that was misspelt as volatile int volatile * causing the pointer to be unqualified...
The qualifiers apply to type of the left left of the qualifier, except if the qualifier itself is the leftmost one, in which case it is as if it was right of the base type i.e.
volatile int *
and
int volatile *
mean the same thing. Therefore in volatile int volatile you can remove one of these. Thus the thing what you need is
volatile int *volatile vipa[10];
Meaning that vipais an array of 10volatile-qualified pointers tovolatileint`s.
What the C99 6.7.3p8/C11 6.7.3p9 means is that an array as itself cannot be volatile - its address is constant, only its elements can be qualified. hence if an array type is qualified, it only applies to its elements. This is even so if a typedef is qualified:
typedef int intarray[5];
const intarray myarray;
will declare myarray as if by
const int myarray[5];
whereas if you'd use typedef for a pointer:
typedef int *intptr;
const intptr myptr;
this qualifier would not affect the pointed-to type but be equivalent to
int *const myptr;
While both volatile int and int volatile are strictly allowed, the C standard prefers the former. C11 n1570 6.7.6.1p3:
EXAMPLE The following pair of declarations demonstrates the difference between a ''variable pointer to a constant value'' and a ''constant pointer to a variable value''.
const int *ptr_to_constant;
int *const constant_ptr;
The contents of any object pointed to by ptr_to_constant shall not be modified through that pointer, but ptr_to_constant itself may be changed to point to another object. Similarly, the contents of the int pointed to by constant_ptr may be modified, but constant_ptr itself shall always point to the same location.
It is further possible to add a type qualifier for an array within the brackets, but only in function parameters, so you can write
void foo(int array[volatile])
which means almost the same and the parameter decays to a qualified pointer
void foo(int *volatile array)
but you can use the static specifier only with the former style.
the explanation is simple.
volatile int * == int volatile *
in this case order does not matter.
So volatile int * volatile x; == int volatile * volatile x;
if you have volatile int volatile * you have already declared it as volatile do the second one is not needed

an error about C struct array in formal parameter

I have got the following code:
struct student_info;
void paiming1(struct student_info student[]);
struct student_info
{
int num;
char name[6];
};
The IDE gives an error
error: array type has incomplete element type ‘struct student_info’
void paiming1(struct student_info student[]);
But if I use void paiming1(struct student_info *student); it works OK. Why is that? I am using GCC.
С language unconditionally requires array element type in all array declarations to be complete. Period.
6.7.6.2 Array declarators
Constraints
1 [...] The element type shall not be an incomplete or function
type. [...]
No exception is made for array declarations used in function parameter lists. This is different from C++ - the latter drops this completeness requirement for function parameter lists
struct S;
void foo(struct S[]); // OK in C++, invalid in C
Considering that in parameter list declaration type T [] is later adjusted to type T * anyway, this requirement might appear to be excessive. (And this is why C++ relaxed it.) Nevertheless, this restriction is present in C language. This is just one of the quirks of C language.
As you already know, you can explicitly switch to the equivalent
void paiming1(struct student_info *student);
form to work around the issue.
Careful reading of the standard makes it clear that in C99 and C11 the declaration is supposed to be a constraint violation. C11 6.7.2.6 Array declarations p1
In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type. The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.
Since this contains references to the * that is valid only in function declarations that are not definitions, and nowhere else, the constraints as whole needs to be taken as applying to parameters.
For C90 the case is more complicated. This is actually discussed in C90 Defect Report 47 from 10 December, 1992.
2 of the 6 declarations given there are
/* 1. */ struct S;
/* 3. */ struct S *g(struct S a[]) { return a; }
and the defect report asks if these are strictly-conforming. It must be noted that unlike the question, these are prototypes that are part of definition rather than just declaration.
Nevertheless, the standard committee responds by saying that
The struct S is an incomplete type (subclause 6.5.2.3, page 62, lines 25-28). Also, an array of unknown size is an incomplete type (subclause 6.5.4.2, page 67, lines 9-10). Therefore, arrays of either of the above are not strictly conforming (subclause 6.1.2.5, page 23, lines 23-24). This makes declarations 3, 4, and 6 not strictly conforming. (But an implementation could get it right.)
As an aside, array parameters are adjusted to pointer type (subclause 6.7.1, page 82, lines 23-24). However, there is nothing to suggest that a not-strictly-conforming array type can magically be transformed into a strictly conforming pointer parameter via this rule.
The types in question can be interpreted two different ways. (Array to pointer conversion can happen as soon as possible or as late as possible.) Hence a program that uses such a form has undefined behavior.
(emphasis mine)
Since this has not been clarified in writing since 1992, we must agree that the behaviour is undefined and therefore the C standard imposes no requirements, and a compiler that successfully compiles this this can still conform to C90.
The committee also states that there is no constraint violation in C90, therefore a C90 conforming compiler need not output any diagnostics.
I've edited the answer; I previously claimed that this would apply to C99 and C11 alike, but the text was changed in C99 as above, therefore this is a constraint violation in C99, C11.
Compiler doesn't know anything about size of struct student_info until it is declared. This should work:
struct student_info
{
int num; //学号
char name[6]; //姓名
char sex[5]; //性别
char adress[20]; //家庭住址
char tel[11]; //电话
int chinese,math,english,huping,pingde,jiaoping,paiming1,paiming2;
double ave,zhongping;
};
void paiming1(struct student_info student[]);
void paiming2(struct student_info student[]);
When you declare it as a pointer using *, compiler knows the size of the argument (its an address).

Is there a difference between the _Atomic type qualifier and type specifier?

Why the standard make that difference?
It seems as both designate, in the same way, an atomic type.
Atomic type specifiers :-:)
Syntax: _Atomic ( type-name );
You can declare an atomic integer like this:
_Atomic(int) counter;
The _Atomic keyword can be used in the form _Atomic(T), where T is a type, as a type specifier equivalent to _Atomic T. Thus, _Atomic(T) x, y; declares x and y with the same type, even if T is a pointer type. This allows for trivial C++0x compatibility with a C++ only _Atomic(T) macro definition as atomic<T>.
Atomic type specifiers shall not be used if the implementation does not support atomic types.
The type name in an atomic type specifier shall not refer to an array type, a function type, an atomic type, or a qualified type.
The properties associated with atomic types are meaningful only for expressions that are lvalues.
If the _Atomic keyword is immediately followed by a left parenthesis, it is interpreted as a type specifier (with a type name), not as a type qualifier.
Atomic type qualifiers :-:)
_Atomic volatile int *p;
It specifies that p has the type ‘‘pointer to volatile atomic int’’, a pointer to a volatile-qualified atomic type.
Types other than pointer types whose referenced type is an object type shall not be restrict-qualified.
The type modified by the _Atomic qualifier shall not be an array type or a function type.
The properties associated with qualified types are meaningful only for expressions that are lvalues.
If the same qualifier appears more than once in the same specifier-qualifier-list, either directly or via one or more typedefs, the behavior is the same as if it appeared only once. If other qualifiers appear along with the _Atomic qualifier in a specifier-qualifier-list, the resulting type is the so-qualified atomic type.
The keyword _Atomic is used, alone, as a type qualifier. An implementation is allowed to relax the requirement of having the same representation and alignment of the corresponding non-atomic type, as long as appropriate conversions are made, including via the cast operator.
Yes. There is a difference. When it is used as type specifier then standard restrict it as (6.7.2.4 p(3)):
The type name in an atomic type specifier shall not refer to an array type, a function type,
an atomic type, or a qualified type.
For example
typedef int arr[5];
arr can be a type name when _Atomic is used as qualifier but can't be used as type name if _Atomic is used as type specifier (like _Atomic (arr))
After many attempts, I have found why this is needed: pointers!
Let's suppose you have:
int foo = 1;
int bar = 2;
int *p = &foo;
Picture that as memory locations, first two holding an int, the last one holding a pointer to the first int. _Atomic makes it so that those memory locations are suited for atomic operations.
For reasons that concern your program, you might want:
foo to be atomic, so that you can, for example, atomically change
foo's value to be 2 instead of 1.
p to be atomic, so that you can, for example, change atomically what p is pointing to, and point to bar instead of foo.
In the first case, to make foo atomic is easy, there is no ambiguity when reading it:
_Atomic int foo;
atomic_store_explicit(&foo , 2, memory_order_release); /* valid atomic op. */
But now you want to make p atomic, if you write:
_Atomic int *p;
... that is not what you want!
That is, as explained above, a non atomic pointer to an atomic int. Strictly speaking, there is no guarantee that this pointer will be correctly aligned to be able to do atomic operations on it (although you'll have hard time to force a compiler to misalign a pointer!). This means that, if you managed to make the pointer misaligned, the atomic operations on it will have a chance to fail. What you want is, on the other hand, an atomic pointer to a int that is non necessary atomic.
So you have to write:
int bar = 2;
_Atomic (int *) p;
atomic_store(&p , &bar); /* now valid atomic operation */
Now you have your atomic pointer!
Note that for the very simple case of making the foo int atomic, you could also have written, any of these 3 declarations, the last one uses the convenience typedef defined in stdatomic.h:
typedef _Atomic int atomic_int;
_Atomic int foo;
_Atomic (int) foo;
atomic_int foo;
I made it "easy to understand" with an int and a pointer to and int, but when you have to deal with
_Atomic (struct foobar *) *q;
You will now know that q itself is not an atomic pointer, but it points to an atomic pointer to a foobar struct!
And so the demonstration:
#include <stdatomic.h>
void test()
{
_Atomic int foo = 1; /* Atomic */
_Atomic int *pf = &foo; /* Non Atomic */
_Atomic int **ppf = &pf; /* Non Atomic */
int bar = 2; /* Non Atomic */
_Atomic (int *) pb = &bar; /* Atomic */
_Atomic (int *) *ppb = &pb; /* Non Atomic */
int *res;
res = atomic_load(ppf); /* Not OK, yields a warning */
res = atomic_load(ppb); /* This is correct */
}
In function ‘test’:
test.c:13:6: warning: assignment from incompatible pointer type [-Wincompatible-pointer-types]
res = atomic_load(ppf);
Indeed, the first atomic_load tries to return a non atomic pointer to an int: the int pointed to is atomic, not the pointer. It could also fail, because there is no guarantee that &pf (the content of ppf) is properly aligned for an atomic operation (although practically here it is, you would have to cast pf to a misaligned int to make it fail).
The second atomic_load correctly works with an atomic pointer and returns it to 'res'.

Confusing results from 'sizeof' operator

I recently tried this code and was a little confused. See the following declarations:
static st;
auto au;
register reg;
volatile vl;
const cn;
They all are allocating memory of 4 bytes (on 32 bit GCC). But when i try to print (using printf function) their sizes, they are not working and giving errors.
sizeof(const) // worked and printed 4
sizeof(volatile) // worked and printed 4
sizeof(auto) // error: expected expression before ‘auto’
sizeof(static) // error: expected expression before ‘static’
sizeof(register) // error: expected expression before ‘register’
My doubt is auto, static, register keywords also allocating memory of 4 bytes(on 32 bit arch).
But why these are giving errors unlike const and volatile?
In C prior to the 1999 standard, an unspecified type would default to int in many contexts.
C99 dropped that rule, and omitting the type is now illegal (strictly speaking, it's a constraint violation, requiring a diagnostic -- which could be a non-fatal warning). In any case, omitting the int type has always been a bad idea. (It goes back to C's predecessor languages BCPL and B, which where largely typeless.)
static st;
auto au;
register reg;
volatile vl;
const cn;
These declarations are all legal in C90 (and all the variables are of type int), but they're invalid in C99.
sizeof(const)
sizeof(volatile)
Somewhat to my surprise, these are actually legal in C90 (but not in C99). const or volatile by itself is a type name, equivalent to const int and volatile int, respectively. Syntactically, const and volatile are type qualifiers.
sizeof(auto)
sizeof(static)
sizeof(register)
The distinction is that this:
const int x = 42;
defines x to be an object of type const int, while this:
static int x = 42;
defines x to be a static object of type int (static isn't part of the type).
These are all syntax errors, because auto, static, and register are not type names. Those keywords are storage-class specifiers.
This explains why the first two sizeof expressions seem to work, and the others do not. But that's not particularly useful to know, because if you specify the type int (which you always should), it doesn't matter that sizeof(const) happens to be valid (in C90, not in C99).
The bottom line is that you should always specify the type in any declaration. And though you can legally write sizeof (const int), it's guaranteed to be the same as sizeof (int), so there's not much point in using const in that context.
Prior to C99 if you did not specify a type then int would be implied which is what is happening in your code. It looks like in practice even in C99 mode gcc and clang will just produce warnings. This is a case where compiler warnings are your friend, I tried this in clang -Wall:
printf( "%zu\n", sizeof(const) ) ;
and it warns me:
warning: type specifier missing, defaults to 'int' [-Wimplicit-int]
All the declarations here:
static st;
auto au;
register reg;
volatile vl;
const cn;
also have an implied int type.
We can see that C99 removed the implicit int assumption:
a declaration that lacks a type specifier no longer has int implicitly assumed. The C standards committee decided that it was of more value for compilers to diagnose inadvertent omission of the type specifier than to silently process legacy code that relied on implicit int. In practice, compilers are likely to display a warning, then assume int and continue translating the program.
If we look at the draft C99 standard Forward section paragraph 5 includes the following:
[...]Major changes from the previous edition include:
and has the following bullet:
— remove implicit int
Update
So why does sizeof not like storage class specifiers like static and auto but is okay with type qualifiers like const and volatile, the behavior seems inconsistent with how the declarations work and should the implicit int assumption still work?
Well if we look at the grammar for sizeof in the draft standard section 6.5.3 it is as follows:
sizeof unary-expression
sizeof ( type-name )
So neither a type qualifier nor a storage class specifiers is an expression but a type qualifier is a type-name, if we look at section 6.7.6 the grammar for type-name is as follows:
type-name:
specifier-qualifier-list abstract-declaratoropt
and 6.7.2.1 gives us the grammar for specifier-qualifier-list which is as follows:
specifier-qualifier-list:
type-specifier specifier-qualifier-listopt
type-qualifier specifier-qualifier-listopt <- Bingo allows type qualifier
So we can see that sizeof just does not accept storage class specifiers even if the type is explicitly specified to int, so even the following is an error:
printf( "%zu\n", sizeof(static int) ) ;
and clang tells us:
error: expected expression
printf( "%zu\n", sizeof(static int) ) ;
^
and we can further see that type names won't work with sizeof without ():
printf( "%zu\n", sizeof int ) ;
produces an error:
error: expected expression
but unary expressions work with () as I explained previously here.
The auto, static, register keywords don't identify any type, but modify the way a variable of that type is stored or accessed.
So:
sizeof(auto) // error: expected expression before ‘auto’
sizeof(static) // error: expected expression before ‘static’
sizeof(register) // error: expected expression before ‘register’
make no sense, because you're not requesting the size of any type. Instead:
sizeof(const) // worked and printed 4
sizeof(volatile) // worked and printed 4
These identify types: volatile int and const int. So you can use sizeof on them.
Notice that when you're declaring your variables, the compiler is assuming int as their underlying type. Most compilers (GCC, Clang) will emit warnings if you're relying on this behaviour.
extern, static, auto, register are called storage-class-specifier, while const, restrict, volatile are called type-qualifier.
For type-qualifiers, when used without type-specifier, int is implicitly specified in C89.
C89 §3.5.2 Type specifiers
int, signed, signed int, or no type specifiers
These types listed are the same with each other. While no type specifiers has been removed in C99 in the same section:
C99 §6.7.2 Type specifiers
int, signed, or signed int
Your declarations are all invalid, so the results are largely irrelevant.
The size of a variable/object depends on its data type, such as int or float. The keywords you tried modify the way the compiler handles the variable/object, but they do not alter or dictate its type (therefore they have no bearing on its size).
For your const and volatile declarations, the compiler was likely defaulting to type int (but that's not behaviour you should ever rely on).

Which part of the C standard allows this code to compile?

I was bug-fixing some code and the compiler warned (legitimately) that the function dynscat() was not declared — someone else's idea of an acceptable coding standard — so I tracked down where the function is defined (easy enough) and which header declared it (none; Grrr!). But I was expecting to find the details of the structure definition were necessary for the extern declaration of qqparse_val:
extern struct t_dynstr qqparse_val;
extern void dynscat(struct t_dynstr *s, char *p);
extern void qqcat(char *s);
void qqcat(char *s)
{
dynscat(&qqparse_val, s);
if (*s == ',')
dynscat(&qqparse_val, "$");
}
The qqcat() function in the original code was static; the extern declaration quells the compiler warning for this snippet of the code. The dynscat() function declaration was missing altogether; again, adding it quells a warning.
With the code fragment shown, it's clear that only the address of the variable is used, so it makes sense at one level that it does not matter that the details of the structure are not known. Were the variable extern struct t_dynstr *p_parseval;, you'd not be seeing this question; that would be 100% expected. If the code needed to access the internals of the structure, then the structure definition would be needed. But I'd always expected that if you declared that the variable was a structure (rather than a pointer to the structure), the compiler would want to know the size of the structure — but apparently not.
I've tried provoking GCC into complaining, but it doesn't, even GCC 4.7.1:
gcc-4.7.1 -c -Wall -Wextra -std=c89 -pedantic surprise.c
The code has been compiling on AIX, HP-UX, Solaris, Linux for a decade, so it isn't GCC-specific that it is accepted.
Question
Is this allowed by the C standard (primarily C99 or C11, but C89 will do too)? Which section? Or have I just hit on an odd-ball case that works on all the machines it's ported to but isn't formally sanctioned by the standard?
What you have is an incomplete type (ISO/IEC 9899:1999 and 2011 — all these references are the same in both — §6.2.5 ¶22):
A structure or union type of unknown content (as described in §6.7.2.3) is an incomplete
type.
An incomplete type can still be an lvalue:
§6.3.2.1 ¶1 (Lvalues, arrays, and function designators)
An lvalue is an expression with an object type or an incomplete type other than void; ...
So as a result it's just like any other unary & with an lvalue.
Looks like a case of taking the address of an object with incomplete type.
Using pointers to incomplete types is totally sane and you do it each time you use a pointer-to-void (but nobody ever told you :-)
Another case is if you declare something like
extern char a[];
It is not surprising that you can assign to elements of a, right? Still, it is an incomplete type and compilers will tell you as soon as you make such an identifier the operand of a sizeof.
your line
extern struct t_dynstr qqparse_val;
Is an external declaration of an object, and not a definition. As an external object it "has linkage" namely external linkage.
The standard says:
If an identifier for an object is declared with no linkage, the type for the object shall be complete by the end of its declarator, ...
this implies that if it has linkage the type may be incomplete. So there is no problem in doing &qqparse_val afterwards. What you wouldn't be able to do would be sizeof(qqparse_val) since the object type is incomplete.
A declaration is necessary to "refer" to something.
A definition is necessary to "use" something.
A declaration may provide some limited definition as in "int a[];"
What stumps me is:
int f(struct _s {int a; int b;} *sp)
{
sp->a = 1;
}
gcc warn the 'struct _s' is declared inside parameter list. And states "its scope is ONLY this definition or declaration,...". However it does not give an error on "sp->a" which isn't in the parameter list. When writing a 'C' parser I had to decide where the definition scope ended.
Focusing on the first line:
extern struct t_dynstr qqparse_val;
It can be divided into the separate steps of creating the type and the variable, resulting in this equivalent pair of lines:
struct t_dynstr; /* declaration of an incomplete (opaque) struct type */
extern struct t_dynstr qqparse_val; /* declaration of an object of that type */
The second line looks just like the original, but now it's referring to the type that already exists because of the first line.
The first line works because that's just how opaque structs are done.
The second line works because you don't need a complete type to do an extern declaration.
The combination (second line works without the first line) works because combining a type declaration with a variable declaration works in general. All of these are using the same principle:
struct { int x,y; } loc; /* define a nameless type and a variable of that type */
struct point { int x,y; } location; /* same but the type has a name */
union u { int i; float f; } u1, u2; /* one type named "union u", two variables */
It looks a little funny with the extern being followed immediately by a type declaration, like maybe you're trying to make the type itself "extern" which is nonsense. But that's not what it means. The extern applies to the qqparse_val in spite of their geographical separation.
Here's my thoughts relative to the standard (C11).
Section 6.5.3.2: Address and indirection operators
Constraints
Paragraph 1: The operand of the unary & operator shall be either a function designator, the result of a [] or unary * operator, or an lvalue that designates an object that is not a bit-field and is not declared with the register storage-class specifier.
Paragraph 2: The operand of the unary * operator shall have pointer type.
Here, we don't specify any requirement on the object, other than that it is an object (and not a bitfield or register).
On the other hand, let's look at sizeof.
6.5.3.4 The sizeof and _Alignof operators
Constraints
Paragraph 1: The sizeof operator shall not be applied to an expression that has function type or an incomplete type, to the parenthesized name of such a type, or to an expression that designates a bit-field member. The _Alignof operator shall not be applied to a function type or an incomplete type.
Here, the standard explicitly requires the object to not be an incomplete type.
Therefore, I think this is a case of what is not explicitly denied is allowed.

Resources