Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undefined behaviour with const_cast

I was hoping that someone could clarify exactly what is meant by undefined behaviour in C++. Given the following class definition:

class Foo
{
public:
    explicit Foo(int Value): m_Int(Value) { }
    void SetValue(int Value) { m_Int = Value; }

private:
    Foo(const Foo& rhs);
    const Foo& operator=(const Foo& rhs);

private:
    int m_Int;
};

If I've understood correctly the two const_casts to both a reference and a pointer in the following code will remove the const-ness of the original object of type Foo, but any attempts made to modify this object through either the pointer or the reference will result in undefined behaviour.

int main()
{
    const Foo MyConstFoo(0);
    Foo& rFoo = const_cast<Foo&>(MyConstFoo);
    Foo* pFoo = const_cast<Foo*>(&MyConstFoo);

    //MyConstFoo.SetValue(1);   //Error as MyConstFoo is const
    rFoo.SetValue(2);           //Undefined behaviour
    pFoo->SetValue(3);          //Undefined behaviour

    return 0;
}

What is puzzling me is why this appears to work and will modify the original const object but doesn't even prompt me with a warning to notify me that this behaviour is undefined. I know that const_casts are, broadly speaking, frowned upon, but I can imagine a case where lack of awareness that C-style cast can result in a const_cast being made could occur without being noticed, for example:

Foo& rAnotherFoo = (Foo&)MyConstFoo;
Foo* pAnotherFoo = (Foo*)&MyConstFoo;

rAnotherFoo->SetValue(4);
pAnotherFoo->SetValue(5);

In what circumstances might this behaviour cause a fatal runtime error? Is there some compiler setting that I can set to warn me of this (potentially) dangerous behaviour?

NB: I use MSVC2008.

like image 233
Munro Avatar asked Sep 08 '11 14:09

Munro


2 Answers

I was hoping that someone could clarify exactly what is meant by undefined behaviour in C++.

Technically, "Undefined Behaviour" means that the language defines no semantics for doing such a thing.

In practice, this usually means "don't do it; it can break when your compiler performs optimisations, or for other reasons".

What is puzzling me is why this appears to work and will modify the original const object but doesn't even prompt me with a warning to notify me that this behaviour is undefined.

In this specific example, attempting to modify any non-mutable object may "appear to work", or it may overwrite memory that doesn't belong to the program or that belongs to [part of] some other object, because the non-mutable object might have been optimised away at compile-time, or it may exist in some read-only data segment in memory.

The factors that may lead to these things happening are simply too complex to list. Consider the case of dereferencing an uninitialised pointer (also UB): the "object" you're then working with will have some arbitrary memory address that depends on whatever value happened to be in memory at the pointer's location; that "value" is potentially dependent on previous program invocations, previous work in the same program, storage of user-provided input etc. It's simply not feasible to try to rationalise the possible outcomes of invoking Undefined Behaviour so, again, we usually don't bother and instead just say "don't do it".

What is puzzling me is why this appears to work and will modify the original const object but doesn't even prompt me with a warning to notify me that this behaviour is undefined.

As a further complication, compilers are not required to diagnose (emit warnings/errors) for Undefined Behaviour, because code that invokes Undefined Behaviour is not the same as code that is ill-formed (i.e. explicitly illegal). In many cases, it's not tractible for the compiler to even detect UB, so this is an area where it is the programmer's responsibility to write the code properly.

The type system — including the existence and semantics of the const keyword — presents basic protection against writing code that will break; a C++ programmer should always remain aware that subverting this system — e.g. by hacking away constness — is done at your own risk, and is generally A Bad Idea.™

I can imagine a case where lack of awareness that C-style cast can result in a const_cast being made could occur without being noticed.

Absolutely. With warning levels set high enough, a sane compiler may choose to warn you about this, but it doesn't have to and it may not. In general, this is a good reason why C-style casts are frowned upon, but they are still supported for backwards compatibility with C. It's just one of those unfortunate things.

like image 138
Lightness Races in Orbit Avatar answered Nov 17 '22 16:11

Lightness Races in Orbit


Undefined behaviour depends on the way the object was born, you can see Stephan explaining it at around 00:10:00 but essentially, follow the code below:

void f(int const &arg)
{
    int &danger( const_cast<int&>(arg); 
    danger = 23; // When is this UB?
}

Now there are two cases for calling f

int K(1);
f(k); // OK
const int AK(1); 
f(AK); // triggers undefined behaviour

To sum up, K was born a non const, so the cast is ok when calling f, whereas AK was born a const so ... UB it is.

like image 5
Nikos Athanasiou Avatar answered Nov 17 '22 14:11

Nikos Athanasiou