a2: the assert on a2 size fails: a2 contains 3 ints instead of 2.
a3: surprinsingly, no error is reported and a3 contains also 3 ints.
When a1 is processed, its type is formed from the incomplete type A, completed with the size 3, deduced from its initializer.
But this seems to update A type itself, so that all further declarations using A are impacted.
Type A becomes a 3 ints array type.
This would explain why a2 contains 3 ints (instead of 2) and declaration of a3 is accepted (and it also contains 3 ints)
There is a similar example in the C standard in the "Initialization" section, that explains the expected behaviour:
EXAMPLE 7 One form of initialization that completes array types involves typedef names. Given the
declaration
typedef int A[]; // OK - declared with block scope
the declaration
A a = { 1, 2 }, b = { 3, 4, 5 };
is identical to
int a[] = { 1, 2 }, b[] = { 3, 4, 5 };
due to the rules for incomplete types.
A subtle one...
a1: is correctly allocated and initialized.
a2: the assert on a2 size fails: a2 contains 3 ints instead of 2.
a3: surprinsingly, no error is reported and a3 contains also 3 ints.
When a1 is processed, its type is formed from the incomplete type A, completed with the size 3, deduced from its initializer. But this seems to update A type itself, so that all further declarations using A are impacted. Type A becomes a 3 ints array type.
This would explain why a2 contains 3 ints (instead of 2) and declaration of a3 is accepted (and it also contains 3 ints)
There is a similar example in the C standard in the "Initialization" section, that explains the expected behaviour: