DRW wrote:One const for each * or .
 is a bit different than * in this respect.
A * takes whatever type was on the left, and replaces it with a pointer to that type. That pointer itself is a 2 byte (or 4/8 byte) variable that stores an address, and that stored address can be modified if it's not const. The syntax allows you to put const immediately after the * to specify that it should not be modified.
A  builds an array of whatever type was on the left. There's no place in the syntax to specify const like there is with *, because there is no pointer here. The array type is like all other variables; it can't be moved in the same way that "int a" can't be moved.
The difference might make some sense with an appeal to the 6502: we can always use absolute
addressing to access variables or arrays (with indexing), but pointers always require indirect
You'll find if you try to assign a  to another  it will tell you that it's an invalid lvalue
which is what the language spec calls variables, sort of. Array types are a non-modifiable lvalue, so you can't assign directly to them, except as part of the initial definition. (It was historically decided in C that arrays should not be assigned to, because that would really mean to copy the whole array, which is usually inefficient. So, they made arrays a special case, and the spec has to mention that over and over. When structs were added later they decided it was okay to assign to structs, though.. the language might seem inconsistent in this way. The implicit conversion of arrays to pointers makes this slightly more confusing too.)
There's an exception here, though. If the  type is a parameter to a function, it implicitly behaves like a non-const pointer instead (modifiable lvalue). The language is actually missing a way to specify this as const†
. This conversion to a pointer type is forced by the location of the passed array being unknown at compile time. If you need const here, though, you can simply use a const * type instead, as arrays can always implicitly convert to pointers anyway. (So: a parameter  is really a pointer with a syntax that unfortunately overrides the true array.)
(The importance of const on a parameter is minor, though. The consequences of reassigning it are local to that function; it won't make a difference in DATA vs RODATA etc. like it would with a variable's definition.)
Edit: I found this, which might explain it better: http://c-faq.com/aryptr/index.html
† I think C99 might have added the option to put const inside the ? Not sure.