Declare an array in your include file omitting the first dimension size:
extern float mvp[][4];
Then define the array following the previous declaration in a translation unit:
float mvp[4][4];
No problem. Until you try to get the size of that array in a file which includes the first declaration. Then you would get:
error: invalid application of 'sizeof' to an incomplete type 'float [][4]'
I understand that arrays decays into pointers to their first element when used as lvalue, that array declarations in function prototypes are actually pointers in disguise but here it's not the case. But the first declaration does not declare a pointer, it declares an "incomplete array type" different from:
extern float (*mvp)[4];
When declaring variables, the compiler just reference a "dummy" base address offset and the associated type that the linker will resolve.
I wonder why this "incomplete array type" – which cannot be incremented like a pointer to array but is also not fully an array since its size cannot be retrieved – would be allowed to exist ?
Why not implicitly convert it to a pointer (just a base address offset) or even better, why not throw an error for omitting the size in the first dimension ?
Quoting this
If expression in an array declarator is omitted, it declares an array of unknown size. Except in function parameter lists (where such arrays are transformed to pointers) and when an initializer is available, such type is an incomplete type (note that VLA of unspecified size, declared with * as the size, is a complete type)
So really, the type is incomplete and waiting to be completed later by a later declaration or tentative definition.