Ordinary int is mutable. An entity that is (not merely claimed to be at some point, but "really is") const is not-mutable-in-theory and if the hardware and software cooperate, often not-mutable-in-fact as well.
Defining a variable using the const qualifier makes it "really" const:
const int c3 = 3;
void f(void) {
const int c4 = 4;
...
}
With "tricky" code (casting away the const-ness) you can convince the system to write, or attempt to write, new values into c3 and/or c4:
void f(void) {
const int c4 = 4;
int *p;
p = (int *)&c3;
*p = 99;
printf("c3 is now %d\n", c3);
}
If you call f() you may, on some systems, find that c3 changes and becomes 99. On other systems you may get a "segmentation fault" or other run-time error.
Change the references to use c4 and the same things can happen—although in practice, few if any systems produce a run-time error. However, you may observe something else entirely: instead of printing c4 is now 99 you may get c4 is now 4.
This last can happen because the compiler is allowed to assume that, having made c4 a const int with value 4, you, the programmer, never changed it. That *p = 99 must not have changed it (even if it did), so the call to printf can be replaced with a different call that just prints 4.
The same is generally true of references to c3: the compiler can assume that since you promised never to change it, you never actually did change it. This can produce surprising results, such as: p == &c3 being true, c3 being 3, and *p being 99. But a run-time error is pretty likely, because most modern compilers and OSes cooperate to stick c3 into a read-only region.
When string literals produce arrays (which is most of the time), C has a quirk. These arrays are not const-qualified, but the characters making up the array are read-only anyway (at least in principle, as with const int c3 = 3;). Like c3, most modern systems manage to make them "really read-only".
(String literals do not produce arrays when they are used as initializers for objects of type array of char. Compare:
char *ronly = "this string is read only";
char rwarray[] = "this string is read/write";
Here ronly is a pointer, not an array, so the string literal produces an array and ronly points to the first character of that array. The underlying array is read-only, even though its type is char [25]. But rwarray is an array, not a pointer, so the string literal fills it in—and sets its size to 26—and it is mutable. You would have to write const char roarray[] = ... to make it immutable. [Some like to spell the type char const [], with the const coming after the data-type. These mean the same thing.])