a = &b is not legal:
As others have pointed out, a = &b
is not legal C. However, this is not because you are not allowed to convert a const int *
(pointer to constant int
) to an int *
(pointer to int
); it is because you are not allowed to do it implicitly. You can do it if you explicitly indicate you want to convert it, by using a cast. (If your compiler does not give you a warning message when you do this without a cast, then you should turn on more warning messages for your compiler. If it still does not give you a warning, it is a bad compiler.)
Specifically, clause 6.5.16.1, paragraph 1, of the C 2011 standard [draft N1570] lists constraints that must be satisfied for assignment statements. In regard to this situation, they essentially say that a const int *
can only be assigned to a const int *
and not to an int *
.
a = (int *) &b is legal:
However, you can write a = (int *) & b
. This satisfies the constraints for casts, listed in 6.5.4 1. And 6.3.2.3 says you may convert a pointer to an object type to a pointer to any other object type, as long as pointer is correctly aligned for the destination type.
However, you cannot then use the pointer; you cannot use *a = 3
because then you are trying to change an object that was defined with const
. 6.7.3 6 says the behavior when doing this is undefined.
const is flexible due to C history:
Why then does C allow it? Well, C was developed and used for many years before const
was added to the language. It had to be retrofitted in, and this required allowing existing code to work and not breaking other features of the language. One characteristic of const
is that although one routine might be passed a const int *
, the actual object it is pointing to was not necessarily defined with const
. For example, I can make int x = 7
and then pass &x
to a routine whose parameter is const int *p
. This means that routine will not change the thing p
points to, but it does not mean other routines will not change it.
One problem with that is shown by the standard strchr
function. This is declared as char *strchr(const char *s, int c);
. It is declared with the const char *s
parameter because we want to be able to pass it pointers to const char
without complaint. Since the parameter is const char *
, we are allowed to pass either const char *
or char *
. And this makes sense because strchr
only looks for things in strings; it does not change the strings.
Suppose I have the non-constant string char foo[] = "abcdef"
. I can use strchr
to search for 'd'
in it by using char *p = strchr(s, 'd')
. But then the result I get back is char *
, not const char *
. strchr
accepted a const char *
but then changed it to a char *
!
In this situation, that works. I passed a char *
, it was automatically converted to const char *
for the function call, and I got back a char *
which is legal for my non-const string. But this funny business where const char *
pointers have to be changed into char *
is really a defect in the C language caused by its history.
Round-trips of adding const and removing it are legal:
Here is another situation:
I write a library for other programs to use.
One routine in the library prepares some data to be used later, say data in some struct foo
. This routine allocates memory, calculates the data, and returns a const struct foo *
. It returns a const
pointer because the user of my library should not change the data.
The user calls this routine, uses the data for a while in their program, and eventually calls another routine in my library to free the data.
The routine to free the data must accept a const struct foo *
, because that is what the user has. If it accepted a struct foo *
, the user would get an error when passing a const struct foo *
to a struct foo *
parameter. But, inside the routine, I convert the const struct foo *
to a non-constant struct foo *
so that I can pass it to free
, which requires a non-constant pointer.
This is legal in C because you are allowed to convert a pointer to any object to a pointer to any other kind of object (provided alignment requirements are satisfied) and back, and when you convert it back, 6.3.2.3 7 guarantees that you have the original pointer back. So, we are allowed to convert a struct foo *
pointer to const struct foo *
, let the user use it for a while, then accept the const struct foo *
in our free-the-object routine, convert it back to the original pointer, which is struct foo *
, and then pass it to free
.