Undefined Behaviour of Const

Undefined behaviour of const

The optimizer can determine that a is a constant value, and replace any reference to it with the literal 3. That explains what you see, although there's no guarantee that's what's actually happening. You'd need to study the generated assembly output for that.

Is this undefined behavior with const_cast?

Quote from cppreference:

Even though const_cast may remove constness or volatility from any pointer or reference, using the resulting pointer or reference to write to an object that was declared const or to access an object that was declared volatile invokes undefined behavior.

So yes, modifying constant variables is undefined behavior. The output you see is caused by the fact that you tell the compiler that the value of a will never change, so it can just put a literal 0 instead of the variable a in the cout line.

Undefined behaviour accessing const ptr sometimes

It seems that it was completely coincidental that smaller allocations were always addressed in a spot that would not get erased by the rep stosd instruction present in printf. Not caused by strange compiler optimisations as I first thought it was.

What does the "rep stos" x86 assembly instruction sequence do?

I also have no idea why I decided to do it this way. Not exactly the question I asked but I ultimately wanted a compile time lookup table so the real solution was static inline constexpr auto arr = B<size>() on c++20. Which is why the code looks strange.

If changing a const object is undefined behavior then how do constructors and destructors operate with write access?

The standard explicitly allows constructors and destructors to deal with const objects. from 12.1/4 "Constructors":

A constructor can be invoked for a const, volatile or const volatile object. ... const and volatile semantics (7.1.5.1) are not applied on an object under construction. Such semantics only come into effect once the constructor for the most derived object (1.8) ends.

And 12.4/2 "Destructors":

A destructor can be invoked for a const, volatile or const volatile object. ... const and volatile semantics (7.1.5.1) are not applied on an object under destruction. Such semantics stop being into effect once the destructor for the most derived object (1.8) starts.

As background, Stroustrup says in "Design and Evolution of C++" (13.3.2 Refinement of the Defintion of const):

To ensure that some, but not all, const objects could be placed read-only memory (ROM), I adopted the rule that any object that has a constructor (that is, required runtime initialization) can't be place in ROM, but other const objects can.

...

An object declared const is considered immutable from the completion of the constructor until the start of its destructor. The result of a write to the object between those points is deemed undefined.

When originally designing const, I remember arguing that the ideal const would be an object that is writable until the constructor had run, then becomes read-only by some hardware magic, and finally upon the entry into the destructor becomes writable again. One could imagine a tagged architecture that actually worked this way. Such an implementation would cause a run-time error if someone could write to an object defined const. On the other hand, someone could write to an object not defined const that had been passed as a const reference or pointer. In both cases, the user would have to cast away const first. The implication of this view is that casting away const for an object that was originally defined const and then writing to it is at best undefined, whereas doing the same to an object that wasn't originally defined const is legal and well defined.

Note that with this refinement of the rules, the meaning of const doesn't depend on whether a type has a constructor or not; in principle, they all do. Any object declared const now may be placed in ROM, be placed in code segments, be protected by access control, etc., to ensure that it doesn't mutate after receiving its initial value. Such protection is not required, however, because current systems cannot in general protect every const from every form of corruption.

Is modifying the internal bytes of a const object undefined behavior in case it contains another object constructed by placement new?

This is Undefined Behavior.

From [dcl.type.cv],

Any attempt to modify a const object during its lifetime results in
undefined behavior.

Adding the mutable specifier to buffer will allow it to be modified by a const member function.

Undefined behaviour with const_cast

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.

Where, specifically, does the standard state that modifying a const object is undefined behaviour?

The C++17 standard states in chapter 10 "Declarations" under [dcl.type.cv] 10.1.7:


  1. Except that any class member declared mutable (10.1.1) can be modified, any attempt to modify a const object during its lifetime (6.8) results in undefined behavior.

Is const-casting via a union undefined behaviour?

It's implementation defined, see C99 6.5.2.3/5:

if the value of a member of a union object is used when the most
recent store to the object was to a different member, the behavior is
implementation-defined.

Update: @AaronMcDaid commented that this might be well-defined after all.

The standard specified the following 6.2.5/27:

Similarly, pointers to qualified or unqualified versions of compatible
types shall have the same representation and alignment
requirements.27)

27) The same representation and alignment requirements are meant to
imply interchangeability as arguments to functions, return values from
functions, and members of unions.

And (6.7.2.1/14):

A pointer to a union object, suitably converted, points to each of its
members (or if a member is a bitfield, then to the unit in which it
resides), and vice versa.

One might conclude that, in this particular case, there is only room for exactly one way to access the elements in the union.

Is swapping a const member undefined behavior? C++17

This seems to be an area of c++ evolution.

It's UB in c++17 because of this restriction on replacing const member objects

However, in later versions, this restriction is mostly removed.. There remains a restriction on complete const objects. For instance you can't do this if MyInt i0(0); was const MyInt i0(0);

Even though c++20 now allows modification of const sub objects, it's best to avoid const_cast and use this to create an assignment ctor. In this case one doesn't need a destructor since it's trivially destructable. Note that one had to use placement new prior to c++20 and that was still UB since const sub objects were not permitted to change previously.

constexpr MyInt& operator=(MyInt&& rh)
{
std::construct_at(&this->i, rh.i);
return *this;
}


Related Topics



Leave a reply



Submit