Sujet : Re: So You Think You Can Const?
De : david.brown (at) *nospam* hesbynett.no (David Brown)
Groupes : comp.lang.cDate : 08. Jan 2025, 09:46:46
Autres entêtes
Organisation : A noiseless patient Spider
Message-ID : <vlle1n$2n1b0$1@dont-email.me>
References : 1
User-Agent : Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.11.0
On 07/01/2025 20:32, Julio Di Egidio wrote:
Hi everybody,
I am back to programming in C after many years:
indeed I have forgotten so many things, including
how much I love this language. :)
In particular, I am using C90, and compiling with
`gcc ... -ansi -pedantic -Wall -Wextra` (as I have
the requirement to ideally support any device).
What devices do not have at least C99 compilers available - and yet /do/ have standard C90 compilers available? What sort of code are you writing that should ideally run on an AVR Tiny with 2K of flash and 64 bytes of ram, a DSP with 24-bit chars, and a TOP100 supercomputer? Have you thought about this in more detail?
People who say they want their code to run on anything are invariably wildly exaggerating. People who say they want to write strictly standards-conforming code, especially C90, so that it will run everywhere, misunderstand the relationship between the C standards and real-world tools.
I would say that the most portable language standard to use would be a subset of C99. Avoid complex numbers, VLAs, and wide/multibyte characters, and it will be compilable on all but the most obscure compilers. The use of <stdint.h> types make it far easier to write clear portable code while keeping good efficiency, and many C99 features let you write clearer, safer, and more efficient code. C90 was probably a good choice for highly portable code 15-20 years ago, but not now. (Your use of "malloc" eliminates far more potential devices for the code than choosing C99 ever could.)
To the question, I was reading this, but I am not
sure what the quoted passage means:
Matt Stancliff, "So You Think You Can Const?",
<https://matt.sh/sytycc>
<< Your compiler, at its discretion, may also choose
to place any const declarations in read-only storage,
so if you attempt to hack around the const blocks,
you could get undefined behavior. >>
I do not understand if just declaring that a pointer
is to constant data may incur in that problem even
if the pointed data was in fact allocated with malloc.
I would say of course not, but I am not sure.
You are mixing up declarations and definitions. It's not surprising - the article you reference is full of mistakes and bad advice.
If you write "const uint32_t hello = 3;", you are /defining/ the object "hello", so it is a const object. It's value may not be changed in any way during its lifetime - attempting to do so is undefined behaviour, and the compiler will complain if it sees a direct attempt to change it. If the const object has program lifetime - it is a file-scope variable, or a static variable - the compiler can put it in read-only memory of some sort. On a microcontroller, that might mean flash memory - on a PC, it might mean a write-protected read-only memory page. For a local variable, it's much more likely that it will go in a register, on the stack, or be eliminated by optimisation - but if the initialiser is always the same, it could hypothetically also be placed in read-only memory.
Suppose you have :
int v = 123; // Non-const object definition
const int * cp = &v; // Const pointer to non-const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Change the target data
This is allowed, because the original object definition was not a const definition.
However, with this:
int v = 123; // Const object definition
const int * cp = &v; // Const pointer to const data
int * p = (int *) cp; // Cast to non-const pointer
*p = 456; // Undefined behaviour
You can make the pointer to non-const, but trying to change an object that was /defined/ as const is undefined behaviour (even if it was not placed in read-only memory).
When you use dynamic memory, however, you are not defining an object in the same way. If you write :
const int * cp = malloc(sizeof(int));
you are defining the object "p" as a pointer to type "const int" - but you are not defining a const int. You can cast "cp" to "int *" and use that new pointer to change the value.
E.g. consider this little internal helper of mine
(which implements an interface that is public to
do an internal thing...), where I am casting to
pointer to non-constant data in order to free the
pointed data (i.e. without warning):
```c
static int MyStruct_free_(MyStruct_t const *pT) {
assert(pT);
free((MyStruct_t *)pT);
return 0;
}
```
Assuming, as said, that the data was originally
allocated with malloc, is that code safe or
something can go wrong even in that case?
The code is safe in that it is not undefined behaviour - data allocated with malloc is never defined const. However, it is /unsafe/ in that it is doing something completely unexpected, given the function signature.
When you have a function with a parameter of type "const T * p", this tells people reading it that the function will only read data via "p", and will never use "p" to change the data. The compiler will enforce this unless you specifically use casts to tell the compiler "I'm doing something that looks wrong - but I know it is right".
Don't lie to your compiler. Don't lie to your fellow programmers (or yourself). Use const for things that you won't change - it is extremely rare that it is appropriate to cast away constness.