Path: csiph.com!eternal-september.org!feeder.eternal-september.org!nntp.eternal-september.org!.POSTED.mailhub.linuxsc.com!not-for-mail From: Tim Rentsch Newsgroups: comp.lang.c Subject: Re: pedantic gcc and const 2D arrays Date: Mon, 27 Apr 2026 07:27:46 -0700 Organization: A noiseless patient Spider Message-ID: <86qzo01nrx.fsf@linuxsc.com> References: <20260409012107.00006dc5@yahoo.com> <10r76oj$1vci$1@dont-email.me> <10r7s3h$7jht$2@dont-email.me> <20260409140602.00007b72@yahoo.com> <10r912i$jtv1$1@dont-email.me> <10rgobr$2o806$1@dont-email.me> <864ilf823x.fsf@linuxsc.com> <10sltgu$1s5u8$1@dont-email.me> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Injection-Info: dont-email.me; posting-host="mailhub.linuxsc.com:45.79.96.183"; logging-data="2562830"; mail-complaints-to="abuse@eternal-september.org" User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.4 (gnu/linux) Cancel-Lock: sha1:azLENn1FaWHZSbwpqTSQ8ggWQVQ= Xref: csiph.com comp.lang.c:398032 Andrey Tarasevich writes: > On Mon 4/13/2026 5:40 AM, Tim Rentsch wrote: > >> But arrays are another >> kettle of fish altogether, and I'm happy to see the issues around >> const arrays are finally getting the attention they deserve (which I >> have been advocating for more than 10 years now). > > What I still don't understand is some remaining details about this > specific conversion for array pointers, even after C23 changes. > > Previously I wrote: > > "[...] now const-qualification applies not only to the elements, but > also to the entire array type as well. [...] It now falls under the > "usual" const-correctness rules. In C23 arrays are no longer special > in such contexts" > > But on the second thought, this is oversimplifying things. What about > remaining rules of type compatibility wrt const-qualification of array > elements? I think I see where you're going here. Let's see if we can clear up the seeming contradictions. Forgive me if I meander a bit. > Again, when we do something like this: > > typedef int A[10]; > A a; > const A *pa = &a; > > this is equivalent, speaking informally, to > > int a[10]; > const int (const *pa)[10] = &a; > > i.e. as C23 states, the const-qualification is applied to both the > array itself and to its elements. (Yes, I know that the `pa` > declaration is formally invalid. This version is intended as a > sketch.) I'd like to rewrite these two lines, using a slightly different syntax to help the subsequent explanation: int [10] a; int const [10] const *pa = &a; In this syntax declarations are read right-to-left, with each 'const' applying to what is to its immediate left. > So, the `const` that applies to the array itself does indeed fall > under the "usual const-correctness rules" - we are just adding `const` > in a pointer conversion. Nothing to see here. > > But what about the `const` on the array elements? It is still there > even in C23. Good old C type compatibility rules state that for two > arrays types to be compatible their element types have to be > compatible. And the latter requires identical qualification. To address this question let's change the scenario slightly. First I want to be able to write a 'struct' type without the keyword, and also with the rule that keyword-less structs are the same if their members are the same. Thus, the two declarations { int x; } foo = {1}; { int x; } *pfoo = &foo; are allowed, because the two keyword-less struct types are in essence the same type. Now some observations about the type of foo. If somewhere else there were a separate declaration for foo (perhaps in a header file), it could not be written as either extern { int const x; } foo; or as extern { int x; } const foo; because of type compatibility rules. Of course the point of considering these alternatives is to reason analogously to array types, but with the freedom to have the const-ness of the "element" type be independent of the aggregate type. Given the previous declaration/definition for foo, let's say at file scope, we could declare a second variable cfoo -- this time as being local to a function, and also const-qualified -- like so { int x; } const cfoo = foo; because the "element" type of cfoo matches the element type of foo, even though the aggregate types are different. Now we want to ask about the type of cfoo.x. Obviously that type must be int. But is it? It's kind of yes and no. If we take the address of cfoo.x, it looks like its type cannot be int, because this attempt to use it int *pcfoo_s_x = &cfoo.x; // doesn't work fails, for the obvious reason that we don't want to be able to modify a const object by changing the "element" cfoo.x by using the pointer pcfoo_s_x. So, sort of like arrays, the const-ness of the outer keyword-less struct "falls through", in a sense, to the type of the member x. Let's enlarge the focus a bit. Surely we expect to be able to write this declaration { int x; } const *p_unassignable_foo = &foo; following the usual rules of address conversion (a pointer of type X* can be converted to a pointer of type const X*). But wait! We already agreed that a second declaration of foo with extern { int x; } const foo; isn't allowed, because of type incompatibility. So why is it that the address of foo can be converted to a pointer to an _incompatible_ type in the assignement to p_unassignable_foo? Of course the simple answer is that the language allows it. The more satisfying answer is that the language allows the conversion because it is safe, even though the pointed-to types are not compatible. The const-ness of the "element" x is not relevant to the question -- it's safe to assign a X* to a const X*, and thus it's allowed, with the caveat that to make it work we have to insist that an assignment like p_unassignable_foo->x = 17; be disallowed. I expect you can run this explanation backwards to the array case, and see why a pointer to an array can be converted to a pointer to a C23-const array makes sense, even though there is a level of type incompatibility involved in the two types involved. The conversion is safe, and that's why it's allowed; the const-ness of the element type is just a detail to be considered in deciding how to make the conversion not violate what we expect for const-qualified types. > In the above case, since in C23 `const` continues to fall-through to > array elements, const-qualification of the element type does not > match: one array type has `int` elements, another - `const int` > elements. So, why is > > const A *pa = &a; > > valid in C23? Am I missing another little change somewhere in C23? The answer is the compatibility of the pointed to types doesn't matter. The conversion of the pointer type is allowed because it preserves our expectation for how const-qualified types behave, and the compability of the pointed-to types was just a red herring. I'm sorry to take so long to get to the point. My explanation reflects the thought process that I went through to understand and then answer your question. I hope it has cleared up how to understand the new rules for array const-ness.