tags:

views:

450

answers:

9

I find that in the following code snippet

const int i = 2;  
const int* ptr1= &i;  
int* ptr2 = (int*)ptr1;  
*ptr2 =3;

i's value changes to 3. What I could like to know is why is this allowed. What are the situations in which this could become helpful?

+29  A: 

It's allowed because you have overruled the constness of ptr1 by casting it to a non-const pointer. This is why casts can be very dangerous.

Note that some compilers, such as GCC, will not allow you to cast away const status like this.

Graham Borland
+1 good answer.
acidzombie24
Note that this code might crash if the const value is stored in read-only memory.
Georg
My gcc (4.4.3) accepts the code without any complaint. codepad's compiler also accepts it ( http://codepad.org/V9Oi65Cf ) as does ideone's ( http://ideone.com/tjCq7 )
pmg
@georg is it not guaranteed that "const int i=2" will store it in read-only-memory?
Manas
@Manas No, the C and C++ standards don't guarantee much of anything about the layout of data in the resulting executable, in order to allow for maximum flexibility across platforms.
Tyler McHenry
@Manas: no. Some architectures don't even have read-only memory. Footnote 114 to section 6.7.3/2 says, "The implementation may place a const object that is not volatile in a read-only region of storage. Moreover, the implementation need not allocate storage for such an object if its address is never used."
Steve Jessop
You should note that when you cast-away constness and try to overwrite the data, you get undefined behavior.
John Dibling
@Graham: GCC will accept this code, a C-cast is **evil** but supersedes `reinterpret_cast` and `const_cast`.
Matthieu M.
Changing the value of a `const` variable is not allowed. Any attempts to do so result in undefined behaviour. That the compiler does not complain is because you told it to shut up by using a typecast.
Bart van Ingen Schenau
C++ should disallow this. In C, it's just tough.
Matt Joiner
+12  A: 

You have broken the constantness guarantee by playing pointer tricks. This is not guaranteed to work all the time and could invoke almost any behavior depending on the system/OS/compiler that you throw it at.

Don't do that.

Or at least don't do that unless you really know what you are doing and even then understand that it is not in the least portable.

dmckee
+5  A: 

const really means "readonly".

As you've found out, the value of const objects can change, but you have to use devious methods to do it. And while using these devious methods you invoke Undefined Behaviour.

pmg
@Konrad: In both C and C++ it is undefined behaviour to modify an object that contains a `const`-qualification in its definition. These objects might potentially be located in read-only memory.
Bart van Ingen Schenau
@Bart: you are right of course … somehow I had ignored that this was a variable definition.
Konrad Rudolph
+2  A: 

It works because you have explicitly cast the constness of the pointee away. While ptr1 is a pointer to a const int, ptr2 is a pointer to an int, so it's pointee is changeable.

There are very few good reasons to do this, but you can just about find a case where it avoids code duplication. For instance:

const char* letter_at(char* input, int position)
{
    ... stuff ...
    return &found_char;
}

char* editable_letter_at(char* input, int position)
{
    return (char*)(letter_at(input, position));
}

(Example somewhat mangled from the C++ example in Item 3 of Effective C++ 3rd)

Chris
+2  A: 

If you're going to cast away constness in a C++ program, please use a more C++ style of casting:

int *ptr2 = const_cast<int*>(ptr1);

If you do run into problems related to this type of casting (you will, you always do) then you can find where it happened much rapidly by searching for "const_cast" rather than trying every combination under the sun. Besides, it'll help others our who may or may not come after you.

There are only a few situations where I could see this being helpful. The majority of them are corner cases. I'd avoid this at all costs if you're developing in C++.

wheaties
+9  A: 

"Allowed" is the opposed of "prevented", but it's also the opposite of "forbidden". You've seen that modifying your const object isn't prevented, but that doesn't exactly mean it's allowed.

Modifying a const object isn't "allowed" in the sense of being "permitted". The behaviour of your program is not defined by the standard (see 6.7.3/5). It just so happens that on your implementation, on that run, you saw the value 3. On another implementation or on another day, you might see a different result.

However, it's not "prevented", because with the way C casts work, detecting it at compile time is a halting problem. Detecting it at runtime requires extra checks at all memory accesses. The standard is designed not to impose a lot of overhead on implementations.

The reason casting away const is supported at all, is because if you have a const pointer to a non-const object, the language allows you (in both senses) to modify that object. To do so you need to get rid of the const qualifier. The consequence of this is that programmers can also discard const qualifiers from pointers to objects which actually are const.

Here's a (slightly silly) example of code which discards a const qualifier for that reason:

typedef struct {
    const char *stringdata;
    int refcount;
} atom;

// returns const, because clients aren't allowed to directly modify atoms,
// just read them
const atom *getAtom(const char *s) {
    atom *a = lookup_in_global_collection_of_atoms(s);
    if (a == 0) {
        // error-handling omitted
        atom *a = malloc(sizeof(atom));
        a->stringdata = strdup(s);
        a->refcount = 1;
        insert_in_global_collection_of_atoms(a);
    } else {
        a->refcount++;
    }
    return a;
}

// takes const, because that's what the client has
void derefAtom(const atom *a) {
    atom *tmp = (atom*)a;
    --(tmp->refcount);
    if (tmp->refcount == 0) {
        remove_from_global_collection_of_atoms(a);
        free(atom->stringdata);
        free(atom);
    }
}
void refAtom(const atom *a) {
    ++(((atom*) a)->refcount);
}

It's silly because a better design would be to forward-declare atom, to make pointers to it completely opaque, and provide a function to access the stringdata. But C doesn't require that you encapsulate everything, it allows you to return pointers to fully-defined types, and it wants to support this kind of const usage to present a read-only view of an object that's "really" modifiable.

Steve Jessop
+2  A: 

C casts tell the compiler that you know what you're doing, and that you'll make sure it all works in the end. If you use them without understanding precisely what you're doing, you can get into trouble.

In this case, the compiler is perfectly within its rights to put i in read-only memory, so that this code will crash when run. Alternately, it might work as you saw. The Standard specifies this as undefined behavior, so literally anything might happen.

C was originally designed to write Unix in, and deliberately gives the programmer a great deal of freedom in manipulating data, since in OS writing it's often very useful to write highly implementation-specific code that does things that would be unsafe in any other context. In regular application code, casting should be done with caution.

And don't use C-style casts in C++. C++ has its own family of casts that are easy to search for in code and which often specify more what the cast is actually doing. In this particular case, you'd use const_cast, which shows exactly what you're doing and is easy to find.

David Thornley
+1  A: 

Because C knows that programmers always know what they are doing and always do the right thing. You can even cast it like:

void* ptr2 = (int(*)(void(*)(char*), int[])) ptr1;
(*(int*)ptr2) = 3;

and it will not even complain at all.

tia
+1  A: 

The statement const int i = 2; means that the symbol/variable i holds value 2, and the value cannot be changed using i; but there is no guarantee that the value cannot be changed by some other means (as in OP's example).

ArunSaha