C array types are weird

https://anselmschueler.com/blogposts/2025-c-pointers/

Comments

alexey-salminMay 27, 2026, 3:06 AM
Interestingly the article doesn't mention two-dimensional arrays and they're curios because they bring a certain asymmetry with them. It always tripped me over the most in C because I otherwise find the language very "symmetrical". It often feels like in design of this language the beauty of expressing certain things took priority over readability or safety which I admire in a way. But somehow not in the case of the two-dimensional arrays.

If you see a[i][j] it could mean two completely different things:

1) "a" is a continuous chunk of memory of N*M bytes, so it behaves as char*; a[i][j] == *(a + i*M + j)

2) "a" is an array of char* pointers that point to N completely distinct memory chunks of size M, so it behaves as char**; a[i][j] == *(*(a + i) + j)

With flat arrays the difference between an array as a variable and a pointer to the first element is literally negligible because you won't even see the difference in the assembly. This is why the automatic decay-to-pointer makes a lot of sense.

But that breaks completely with multiple dimensions. You definitely see the difference in the assembly because the memory layout is so different.

flohofwoeMay 27, 2026, 7:19 AM
> "a" is an array of char* pointers that point to N completely distinct memory chunks of size M

The array itself is always a continuous chunk of memory, no matter how many dimensions it has and what the array item type is. The array item type just happens to be a pointer. But a pointer is also just a bunch of bits in memory, like any other type.

ueckerMay 27, 2026, 4:43 AM
"breaks completely"

I rather would say it works nicely in auto-generating the complex indexing operation for n-dimensional arrays which makes it a lot more convenient and less error-prone to write such code. The compiler may also flatten a loop.

The array of pointer hack used previously to similate 2d arrays using an array to pointers to arrays should not be used outside of special algorithms, as it is error prone and slow.

ossopiteMay 27, 2026, 6:53 AM
As I recall, C# supports this in a completely sensible way by distinguishing a[i,j] and a[i][j]. If I understand right, in C, a[i][j] means what C# would spell a[i,j], which does seem rather surprising and inconsistent
ActorNightlyMay 27, 2026, 4:19 AM
I mean, just like with 1 dimensional arrays, it depends on the context.

Array memory is on the stack. The size of that array is actually not known at run time, its only known at compile time, where any reference to that length gets resolved by the compiled.

If your 2d array sits on the stack, then inferring memory layout is pretty easy. If you are dealing with pointer that was passed to a function, then you can't assume anything about data size or limits, which is why many functions that take pointers take a size parameter as well.

alexey-salminMay 27, 2026, 5:35 AM
> If your 2d array sits on the stack, then inferring memory layout is pretty easy. If you are dealing with pointer that was passed to a function, then you can't assume anything about data size or limits, which is why many functions that take pointers take a size parameter as well.

Right, but 2d arrays come into this picture with their own quirks again. You're not just passing the size as the parameter, you can pass it as a "special" parameter that influences how the compiler will interpret other parameters. E.g. in C99 you can do this:

    void do(size_t x, size_t y, int a[][y]);
Here "y" plays the critical role because it will be used to compute offsets in the a[i][j] expression. For 1d arrays this doesn't happen.

Of course it's still generalizable as "all but the outermost dimensions should be known" and for 1d array the outermost dimension is the only dimension. Still, this whole thing always felt a bit odd to me.

ueckerMay 25, 2026, 5:49 AM
In practice, the [static n] notation can give you useful warnings and bounds checking.

https://godbolt.org/z/PzcjW4zKK

And while the (*array_ptr)[3] notation take a moment to get used to, it is very logical. If you have a pointer to an array, you dereference it first and then indx into it. Again, useful for bounds checking: https://godbolt.org/z/ao1so9KP7

kazinatorMay 27, 2026, 2:38 AM
The parentheses in (*parray)[i] would be unnecessary if dereferencing used postfix notation.

  Current:       All postfix

  *ptr[3]        ptr[3]*   // indexed access, then deref

  (*ptr)[3]      ptr*[3]   // deref, then indexed access*
keyleMay 26, 2026, 11:20 PM
I know of this notations but I don't see many people using [static n].

Not sure why, maybe it doesn't feel like C anymore, maybe it feels hacky?

typically if you're passed an array you'd want to get more anyway, so you'd get passed a struct. Not sure.

ueckerMay 27, 2026, 4:45 AM
I don't know. I see people increasingly make use of it. The problem was that in the past compilers ignored this completely, so there was simply no point. Nowadays GCC uses it for warning (the length for bounds and "static" for nonnull), so it starts to become useful.
dnauticsMay 26, 2026, 10:20 PM
What is **int[3][5]
kazinatorMay 27, 2026, 2:48 AM
In C declaration syntax, there is a "stem" called declaration specifiers consisting of specifiers and qualifiers. That's where int can appear. After that, there is a declarator. In some cases, multiple declarators separated by a comma, which share the same "stem".

  int a, b, *c; // one stem consisting of "int", three declarators.
The * is declarator syntax for deriving a pointer type. It never appears such that a type specifier would come after it somewhere to the right.

Some languages have extended the C declaration syntax such that the type derivators can be moved from the declarator part to the "stem". For instance, as an alternative to:

  int a[10];
you can write

  int[10] a;
This is how we could get

   **int[3]
as a declarator stem indicating an array of 3 pointers to pointers to int. But it's not in C.
ori_bMay 26, 2026, 10:51 PM
A syntax error. You need a variable name, not a type name, in the middle.
ori_bMay 26, 2026, 11:03 PM
And if you want 'int **arr[a][b]', it's a value that when you say 'x = **arr[m][n]', will evaluate to an int and assign it to x. Postfix has higher precedence than prefix.
fussloMay 26, 2026, 11:49 PM
or a rejected PR
thranceMay 26, 2026, 11:46 PM
A pointer to a pointer to a pointer to a pointer of integers.
fookerMay 27, 2026, 1:04 AM
C array types are weird because C doesn't really need arrays. It's not what C was about.

But if you designed a language in the era where Fortran, THE array language, reigned supreme, nobody would use your language. The mindshare Fortran had is difficult to convey now, half a century later.

Think of it like making a chatbot today and not mentioning AI or LLMs, that's what making a language without arrays would have felt like in 1970.

alexey-salminMay 27, 2026, 3:17 AM
People who do HPC in C actually wish C had proper arrays like Fortran. If your function takes two pointers as inputs instead of two arrays they can alias the same memory and in fact they may alias any other pointer of the same type. Writing into one of them invalidates all the values you have in registers so you have to load them again.

The "restrict" keyword was invented to solve this but it still has weaker semantics than original Fortran arrays. It can still solve a big share of problems, but it never got proper adoption and never even made it into C++.

genewitchMay 27, 2026, 3:51 AM
i learned FORTRAN in an accelerated tech program in 1996-ish in high school.

i used fortran recently to see how "slow" python is, i did matrix multiplies by hand in .c, and .py. Now i didn't write the fortran, the AI did, but i remember enough that i verified what it did was sane, also the other two i wrote did agree with results.

  fortran 1   unit of time
  C       1.7 unit of time
  python  2.2 unit of time
for the same matmuls.

anyhow, 1996-ish. crazy.

kazinatorMay 27, 2026, 12:21 AM
There is a history to it; in one of the predecessor languages, like B, Ritchie actually had arrays that had a hidden pointer to their start. The "array to pointer decay" was actually a real operation that loaded an address from memory, and it was possible to twiddle the bits to relocate an array. One problem with it was no way to initialize such a pointer field that would allow an array to live in dynamically allocated storage (no constructors in the language).

So in short, the bad design (array values produce pointers) was informed by conceptual compability with an earlier design in which that was literally happening.

xenadu02May 27, 2026, 3:05 AM
Not just this it is important to remember that there was no "aha!" moment where C was created whole-cloth by writing the first compiler in B then cross-compiling.

The language B was evolved in-place by adding new features, then editing the compiler source to make use of those new features, then repeating. They simply started calling it "New B". At some point the language had evolved sufficiently that they decided to call it C.

The semantics of arrays were inherited from B and simply never changed. Part of me suspects this was also because it was seen as "clever" at the time. Look ma, we let arrays turn into pointers! Isn't that clever?

When you look at pre-ANSI C function prototypes you wonder "where are the parameter types?" because there are none. The compiler didn't bother to check. Part of that was perhaps for implementation reasons but a big part of that was the feeling or culture inherited from B: in that language you just had words of memory. You were free to interpret any word of memory as any data type you liked. So duh of course it is up to you to decide how many parameters your function received and of what type. If the caller supplied a different number or different types? Don't do that.

If you are coming from that sort of world clever tricks like arrays decaying to pointers or automatically converting between data types and sizes seems perfectly natural. Anything C offers above and beyond that is an improvement from B after all.

AnimatsMay 27, 2026, 2:40 AM
The real lack is that C doesn't have slices. Slices can do most of what pointers into arrays can do, with sane semantics. Slices were invented surprisingly late. They were implementable in the 1970s, but didn't really show up until the 1990s. Now that we have slices, the demand for pointers into the middle of an array has much decreased.

I had a go at retrofitting C with slices over a decade ago.[1] Too much political hassle.

[1] https://www.animats.com/papers/languages/safearraysforc43.pd...

abnercoimbreMay 27, 2026, 3:10 AM
Meaning it died at committee?
jcranmerMay 27, 2026, 3:46 AM
From what I can see in the WG14 document log [1], it never made it to the committee in the first place.

[1] https://www.open-std.org/jtc1/sc22/wg14/www/wg14_document_lo...

AnimatsMay 27, 2026, 4:17 AM
Never pushed it that far. Mentioning safety in a C or C++ context was viewed very negatively back then.
ueckerMay 27, 2026, 4:36 AM
We are working towards this though and a lot of this already works:

https://godbolt.org/z/EP3cP3qGs

ueckerMay 27, 2026, 4:52 AM
It is a struggle though to get the improvements through the committee. Especially the C++ folks from the Clang side fight very hard against it, this is - for example - why we not have forward declarations where I already had weak consensus, but the clang area team made it clear they will never implement it.
eqvinoxMay 27, 2026, 5:00 AM
The vec macros currently there directly call realloc()… that's gonna exclude any custom allocator setups… and the string code uses the vecs :/
ueckerMay 27, 2026, 5:07 AM
The example shown does not use vec from my experimental library.

But yes, I was thinking about making a custom-allocator version of vec.

ueckerMay 27, 2026, 5:09 AM
But since you mentioned it, the interoperation of vec/span and arrays is also really nice in my opinion: https://godbolt.org/z/nYe48jh5d
the__alchemistMay 26, 2026, 10:32 PM
This is one of the things that I feel is an inappropriate abstraction that is around for historical reasons. When I do FFI to call C from rust, I usually wrap the generated API (Which is pointer based) into rust's &[] array syntax. Arrays/lists/Vecs etc in most non-C languages feel like an abstraction over a collection of items; I feel like C's exposing the pointer directly is taking a low-level memory/MMIO operation and inserting it into business logic. Conceptually, I like to keep them separate; pointers for writing drivers, accessing registers, writing to flash memory etc. Arrays/lists/vecs for higher level operations on collections.

Tangent: I have a pet theory that part of Zig's raison d'etre is to fix some of the problems with C, while accommodating its pointer-based data structures, and the resulting patterns.

vulcan01May 26, 2026, 10:41 PM
This talk – "Programming without pointers" – by Andrew Kelley may be interesting to you.

https://www.hytradboi.com/2025/05c72e39-c07e-41bc-ac40-85e83...

throwaway27448May 26, 2026, 11:21 PM
Learning to program with pointers is enormously useful. It's simply bad software engineering to not use typing to enforce constraints on access to pointers (or addresses, or however you'd like to term them)
doyougnuMay 27, 2026, 1:35 AM
IIRC that talk of about using indices (u32) to represent data in an array. That is orthogonal to representing that information in the type system since you can just type the index
throwaway27448May 27, 2026, 7:17 AM
How do you distinguish the array from the index? Is an array not just a pointer and index description?
mlmonkeyMay 27, 2026, 1:23 AM
It still cracks me up that 3[x] and x[3] mean the same thing in C.
jason_sMay 27, 2026, 3:37 AM
yeah that's what I thought the article was going to be about.
jason_sMay 27, 2026, 3:37 AM
From the title, I thought they were going to point out that `a[2]` and `2[a]` have identical meaning in C.
IncreasePostsMay 26, 2026, 10:19 PM
Paging walter bright
WalterBrightMay 27, 2026, 2:21 AM
At your service! D fixed it, and I'm sorry C users have suffered as the array-to-pointer decay blasted their kingdom. Fixing it in C is easy and should be the #1 priority.
glouwbugMay 26, 2026, 10:22 PM
C's biggest mistake.

But in other news most don't know that a[3] == 3[a]

parlortricksMay 27, 2026, 2:02 AM
I didn't understand why a[3] == 3[a], but i found this stackoverflow that explains it.

https://stackoverflow.com/a/16163840

In C a[i] is converted to *(a+i) internally. i[a] is converted to *(i+a). Array names also act as pointers in c. so (a+i) or (i+a) give an address (using pointer arithmetic) that is dereferenced using

parlortricksMay 27, 2026, 6:20 AM
In C a[i] is converted to *(a+i) internally. i[a] is converted to *(i+a). Array names also act as pointers in c. so (a+i) or (i+a) give an address (using pointer arithmetic) that is dereferenced using *
throwaway27448May 26, 2026, 11:20 PM
Even more irrelevant than the array type
fatty_patty89May 26, 2026, 10:17 PM
there's no array type in c
colejohnson66May 26, 2026, 10:28 PM
Yes it does. It just decays to a pointer at the slightest touch.
Mikhail_EdoshinMay 27, 2026, 4:11 AM
There are differences. E.g. va_xxx functionality may be implemented either with a pointer or an array. The difference becomes visible if you try to pass a va_list to another variadic function and then extract it later with va_arg. About half of compilers will happily do that, and another half will refuse to compile the naive version. (There's a more sophisticated proper way.)

https://stackoverflow.com/questions/79897621

throwaway27448May 26, 2026, 11:19 PM
So why are we discussing it
dwatttttMay 26, 2026, 11:48 PM
Because doing a dance to avoid it decaying conveys better information to both the compiler and downstream users of your code.
throwaway27448May 27, 2026, 5:38 AM
So use a language that precludes this
throwaway27448May 26, 2026, 11:19 PM
Why are we still discussing c in 2026? Why are you intentionally hamstringing yourself unless you're using fucking hp-ux
flohofwoeMay 27, 2026, 6:48 AM
Because C is an exceptionally unopinionated language. For some people having a programming language that doesn't have a strong opinion on how things are supposed to work is a good thing.

Most modern language start with fixing C's warts (good) but then at some point turn into 'tech manifestos' (for lack of a better word). C is refreshengly devoid of opinion and that's what makes it so extremely flexible and timeless.

throwaway27448May 27, 2026, 7:07 AM
> Because C is an exceptionally unopinionated language.

Compared to what?

Anyway I find it hard to believe enabling suicide is a good thing

smackeyackyMay 27, 2026, 12:00 AM
Embedded programming is still in C for a lot of micro controllers and whatnot. If you’re programming with limited resources it’s essential to understand pointers and arrays. Likely you won’t be doing anything useful without them
pjmlpMay 27, 2026, 7:09 AM
With very few exceptions like PICs, most of them also support C++, which has nice goodies like span and views, that can be optionally bounds checked.
throwaway27448May 27, 2026, 5:37 AM
I understand certain programmers are chained to C. We should use this opportunity to castigate the people who don't target better languages rather than trying to work with clearly outdated tools.
flohofwoeMay 27, 2026, 6:57 AM
What about just letting people decide for themselves instead of telling them what they should and shouldn't do.
pjmlpMay 27, 2026, 7:10 AM
Some times they need a little help from legislators.
throwaway27448May 27, 2026, 7:07 AM
That's how you end up with shitty products and people claiming other countries are trying to control us
cozzydMay 27, 2026, 3:48 AM
And yet so few other languages attempt to have a stable ABI (other than by...mimicking C).
pjmlpMay 27, 2026, 7:11 AM
C doesn't have a stable ABI, most folks mistake the ABI from OS written in C, with an imaginary C ABI.
throwaway27448May 27, 2026, 5:37 AM
The ABI is not the issue.
cartoonfoxesMay 26, 2026, 11:33 PM
pdpiMay 27, 2026, 12:30 AM
As always, the TIOBE Index is of dubious value. The fact that it ranks Delphi above both Go and Rust should give you an idea of why.
keyleMay 27, 2026, 12:52 AM
Yeah it's rather odd, also jumps like this in ADA makes you wonder

https://www.tiobe.com/tiobe-index/ada/

flohofwoeMay 27, 2026, 6:55 AM
Ada is being discussed a lot on HN as some sort of 'lost utopia', and AFAIK Tiobe doesn't count actual usage but (more or less) mentions.
throwaway27448May 27, 2026, 7:08 AM
C is hardly any path forward
mianosMay 26, 2026, 11:38 PM
p.s. in case you don't want to follow the link, number 2 on the list