A multi-dimensional array typedef is half-modelled, and the half is silent
Found while measuring
[[bug-c-sizeof-of-an-array-typedef-name-answers-the-element-size]] — the
sizeof defect is the visible edge of this, and this is much worse.
Measured, compiler f519214f643f, gcc as oracle
typedef int T2[2][3];
int main(void) {
int before = 111;
T2 v;
int after = 222;
for (i=0;i<2;i++) for (j=0;j<3;j++) v[i][j] = 1000 + i*10 + j;
...
}
pxx sizeof(v)=8 before=1011 after=222
v[0][0]=1000 v[0][1]=1010 v[0][2]=1011 v[1][0]=1010 ...
gcc sizeof(v)=24 before=111 after=222
v[0][0]=1000 v[0][1]=1001 v[0][2]=1002 v[1][0]=1010 ...
Two defects in one, and both are silent.
- Under-allocated. 8 bytes where 24 are needed, so the writes run off the
end and land on
before, an unrelated local.beforecomes back 1011. - Wrong row stride.
v[0][1]reads 1010 — the value written tov[1][0]. Rows are not 3 elements wide, so the two indices alias.
Neither produces a diagnostic and the program exits 0.
Cause, and it is one line doing nothing
cparser.inc, the array-typedef recording site. Its own comment is accurate
about what it does and does not do:
{ Only a single constant `[N]` is modelled; a multi-dim or unsized typedef
array leaves it scalar (unchanged behavior). }
if (tk <> tyRecord) and (tk <> tyPointer) and (CurTok.Kind = tkLBrack) and
(Tokens[TokPos].Kind <> tkRBrack) then
begin
Next;
ci := FindCTypedef(nm);
if ci >= 0 then CTypedefArrLen[ci] := CEvalConstExpr;
if CurTok.Kind = tkRBrack then Next;
end;
CTypedefArrLen is ONE integer (defs.inc: "inherent length N (single
dim)"). The [3] is then swallowed by the enclosing
while (CurTok.Kind <> tkSemicolon) ... do Next, which exists to skip the rest
of a declaration and cannot tell a dimension it should have modelled from
punctuation it should discard.
"leaves it scalar (unchanged behavior)" was true of the code and false of the
outcome. Leaving it scalar would answer 4 and allocate 4 — visibly wrong.
What actually happens is that the FIRST dimension is folded in and the rest are
dropped, which allocates a plausible-looking 2 elements and indexes as if the
type were int[2]. A half-applied model is worse than no model: it produces an
answer in the right neighbourhood, which is the one that survives review.
Why the priority is not the sizeof ticket's
sizeof answering 8 is a wrong number. This is a wrong number, an
under-allocation and a stride error, in ordinary hand-written C. cglm's
mat4 is typedef float mat4[4][4] — the exact shape — and cglm is the
library named in the recording site's own comment as the reason the single-dim
case was modelled at all. Anything doing 3D or linear algebra in C spells its
matrices this way.
For whoever takes it
The direct spelling works: int v[2][3] allocates and indexes correctly, so
the multi-dim machinery exists (MAX_ARR_DIMS, the dimSpan/beStack paths
in ParseCLocalDeclAST). This is a typedef that cannot carry what the
declarator path already knows how to consume, not a missing capability —
normalise-dont-special-case rather than a second path.
If the full model is too large for one sitting, REFUSE the declaration rather
than leaving it. A multi-dimensional array typedef is not modelled error is
strictly better than silent neighbour corruption, and it is honest about the
gap in a way the current comment is not.
Check the sibling spellings before closing: an unsized typedef int T[], a
typedef of an array of a record, and typedef int T3[2][3][4].
Log
- 2026-09-05 — resolved; this names the commit that carried the resolve, which is not always the one that carried the change — commit 5435c14a7.