Consider this code:
#include <stdio.h>
int
main(int argc, char **argv)
{
char a[] = "foo";
const char *b = a;
char *c = (char *)b;
*c = 'a';
printf("%s\n", b);
return 0;
}
That's on a high level equivalent to what you're doing internally. Theoretically char *c = (char *)b; *c = 'a';
is illegal, but in practice it happens to work in this particular case.
See const
as a kind of contract between the writer of the API and the user of that API rather than something that is strictly enforced by the compiler and runtime. A secondary reason for const to exist is that it allows string literals to be put into read-only segments in programs opening up for many useful optimizations (like deduplication of strings), but I'd argue that primarily const is a just reminder for the programmer.
This is why in large projects I've seen adding const to function arguments is sometimes called "const poisoning". You poison some string or struct that is being passed around many different layers of APIs to make a promise that it isn't modified anywhere through the layers. The act of adding const by itself is very valuable to discover where unintended modifications happen. You can always get rid of the const quite easily and break the contract, but by not doing that you make it easier to read and debug your program. I've even done some experiments and compilers don't do optimizations that would be reasonable to do if the compiler expected const to be respected.
Sometimes it's even necessary to cast away const for completely legitimate reasons. For example when you have something like this:
struct foo {
const char *string;
int dynamic;
};
void
foo_dynamic(struct foo *f, int x)
{
f->dynamic = 1;
f->string = malloc(16);
snprintf(&s->string, 16, "%d", x);
}
void
foo_static(struct foo *f, const char *x)
{
f->dynamic = 0;
f->string = x;
}
void
foo_free(struct foo *f)
{
if (f->dynamic)
free((void *)f->string);
free(f);
}
In this case we make the promise in our API that we won't change the contents of what foo->string
points to during the life time of foo
, but we still need to be able to free it if we allocated it ourselves. A language fundamentalistlawyer might say that this is undefined behavior (it is) and there are solutions to achieve the same thing (there are), but this is pretty common in practice.