7

Consider the following program:

#include <iostream>

int main()
{
   int x = 0;
   const int* px = new (&x) const int(0);
   x = 1;
   std::cout << *px;  // 1?
}

It compiles under GCC 4.8 (and produces the "expected" output), but I suspect it's entirely UB because the dynamic object has type const int (which remains part of the type). But, then, if so why isn't the compiler stopping me from violating const-correctness?

Community
  • 1
  • 1
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    Why don't you post this as community wiki rather than a points harvester ?? You have enough points, it seems to satiate, one's ego. Or you are counting the points equated to $$ ? – DumbCoder Apr 02 '14 at 10:23
  • @DumbCoder: I wrote the question and the answer to share knowledge, [as encouraged by SO](http://stackoverflow.com/help/self-answer). I don't see why I shouldn't reap the rep rewards just as I would for any standalone question or answer! Besides that, [it has been impossible to post a question as community wiki for two and a half years](http://blog.stackoverflow.com/2011/08/the-future-of-community-wiki/). I hope that answers your question. – Lightness Races in Orbit Apr 02 '14 at 10:31
  • 2
    @DumbCoder: community wiki is not for avoiding rep. http://blog.stackoverflow.com/2011/08/the-future-of-community-wiki/ says, "The intent of community wiki in answers is to help share the burden of solving a question". There's no particular reason Lightness should make his answer editable by low-rep users in this case. – Steve Jessop Apr 02 '14 at 10:33
  • @DumbCoder: [Also relevant](http://meta.stackexchange.com/q/227290/155739) – Lightness Races in Orbit Apr 02 '14 at 10:58

1 Answers1

6

tl;dr: Yes, it's undefined behavior. No, the compiler doesn't diagnose it.


In general, the compiler won't (and sometimes can't) diagnose UB. More obvious examples of const-correctness violation are in fact ill-formed and can be diagnosed:

#include <iostream>

int main()
{
   const int x = 0;
   x = 1;
   std::cout << x;
}

// g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
// main.cpp: In function 'int main()':
// main.cpp:6:6: error: assignment of read-only variable 'x'
//     x = 1;
//       ^

But, other than that, it won't stop you from performing obvious violations of const-correctness:

#include <iostream>

int main()
{
    const int x = 0;
    *const_cast<int*>(&x) = 1;
    std::cout << x;
}

// Output: 1

So, going back to your code snippet, I wouldn't expect too much in the way of compiler diagnostics there.

Still, your code does invoke undefined behaviour. Let's examine it:

#include <iostream>

int main()
{
   int x = 0;
   const int* px = new (&x) const int(0);
   x = 1;
   std::cout << *px;  // 1?
}

Here's what happens:

  1. An int is created with automatic storage duration, initialised to 0.
  2. The name x refers to this object.
  3. A const int is created with dynamic storage duration, re-using the int's storage.
  4. The int's lifetime ends1, 2.
  5. x now refers to the const int3.
  6. Although the name x has type int, it's now referring to a const int, so the assignment is undefined4.

This is an interesting loophole you can use to "get around" const-correctness and, as long as the original int didn't live in read-only memory, it probably won't even result in a crash.

However, it's still undefined and although I can't see what optimisations may be performed that could break the assignment and subsequent read, you're definitely open to all sorts of unexpected nastiness, such as spontaneous volcanoes in your back garden or all your hard-earned rep being transformed into Pounds Sterling and deposited in my bank account (thanks!).


Footnote 1

[C++11: 3.8/1]: [..] The lifetime of an object of type T ends when:

  • if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
  • the storage which the object occupies is reused or released.

Footnote 2

Note that I did not have to explicitly call the "destructor" on the int object. This is mostly because such objects do not have a destructor, but even if I'd picked a simple class T rather than int, I may not have needed an explicit destructor call:

[C++11: 3.8/4]: A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

Footnote 3

[C++11: 3.8/7]: If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static data member whose type is const-qualified or a reference type, and
  • the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects). [..]

Footnote 4

[C++11: 7.1.6.1/4]: Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior. [..]

(Examples follow that are similar to, but not quite the same as, your code snippet.)

JuJoDi
  • 14,627
  • 23
  • 80
  • 126
Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • "I can't see what optimisations may be performed that could break the assignment and subsequent read". I don't know how smart you're assuming the compiler to be, but: escape analysis shows that `px` is not modified after initialization, it always points to that placement-new const object. So the compiler may treat `*px` like a reference-to-const and replace all uses of `*px` with the initialized value of `*px`, which is `0`. Not gonna cause volcanoes, but the fact that the compiler might do this or might actually load from `*px` in the last line is enough to justify making it UB. – Steve Jessop Apr 02 '14 at 10:15
  • @SteveJessop: Can it do that in the face of possible `operator new`s defined in other translation units? – Lightness Races in Orbit Apr 02 '14 at 10:32
  • Whoops, my rollback/unrollback/rerollbacks aren't being collapsed any more. Oh well. @MattPhillips: I don't see why I shouldn't be able to maintain the "questioner"/"answerer" abstraction. Why is it "annoying"? – Lightness Races in Orbit Apr 02 '14 at 10:35
  • 1
    Because it's as though you're pretending there's two people involved in this thread when there's only one, you? As though your question was recognized as being interesting enough by another expert to provoke a really well-thought-out response. It wasn't, or perhaps any possible such answer was preempted by yours. (At the time of this writing.) It's interesting--I upvoted the Q and the A--but it's all you. I mean I don't think you're really trying to deceive anyone but I had the wrong impression until I got to the end, and so: annoying. – Matt Phillips Apr 02 '14 at 10:42
  • `As though your question was recognized as being interesting enough by another expert to provoke a really well-thought-out response` That is exactly what happened. That I happen to be both experts is besides the point! I honestly don't see why people get so uppity about self-answers. Just enjoy the free knowledge; you're welcome. – Lightness Races in Orbit Apr 02 '14 at 10:47
  • @LightnessRacesinOrbit: I don't know, but I want to say that you can't just overload placement operator new for `const int` and that will affect code in other TUs linked with yours. So I *think* that it can, but I don't want to promise that :-) – Steve Jessop Apr 02 '14 at 10:47
  • @SteveJessop: Yeah I dunno either – Lightness Races in Orbit Apr 02 '14 at 10:48
  • Come to think of it, even if `operator new` is unavailable and hence the compiler doesn't know that the initial value of `*px` is `0`, it can still reorder the read from `*px` to before the write to `x` because of the const-ness of `*px`. – Steve Jessop Apr 02 '14 at 10:50
  • @LightnessRacesinOrbit: I think you're entirely right about self-answered questions, it's SO policy whether everyone likes it or not. But I think the disappointment on reaching the end of your answer is a natural psychological response by someone who *thinks* they're reading a conversation ("two people find this interesting and are interacting") but realise they're reading a monologue ("one person thinks this is interesting"). Kind of inevitable if the answer is longer than the reader's screen is tall. – Steve Jessop Apr 02 '14 at 10:53
  • @SteveJessop: Personally I like abstracting away the fact that I'm both OP and answerer. But, then again, I love abstraction. Would it help if I said I have DID? ;) – Lightness Races in Orbit Apr 02 '14 at 10:57
  • @LightnessRacesinOrbit: besides which, anyone who goes around assuming that two different accounts on any site are necessarily operated by two different people is eventually going to be outwitted by a sock puppet ;-) It's all just heuristics for whether something is important/true, and confirmation by an "independent" source is quite a strong psychological factor even though on the internet it shouldn't be. – Steve Jessop Apr 02 '14 at 11:08
  • @SteveJessop: Quite! So, really, I'm doing you all a favour by helping to train away that expectation... – Lightness Races in Orbit Apr 02 '14 at 11:10
  • You and Tony the Pony. – Steve Jessop Apr 02 '14 at 11:12
  • @SteveJessop: Maybe I _am_ Tony the Pony... #layers #plottwist – Lightness Races in Orbit Apr 02 '14 at 11:14
  • Maybe the whole of SO is Jon Skeet's less intelligent sidekick. – Steve Jessop Apr 02 '14 at 11:17
  • @SteveJessop: Or we are all simply figments of Jon's imagination as he daydreams about having a sidekick. – Lightness Races in Orbit Apr 02 '14 at 11:21
  • 1
    Personally I don't enjoy reading meta debates in the comments. Can't we stick to the substance and move that other stuff where it belongs? – david.pfx Apr 05 '14 at 23:44
  • @david.pfx: Allergic to fun? Policeman of the world? Nobody forced you to read anything. Chill out, officer. I don't come here to entertain you. – Lightness Races in Orbit Apr 06 '14 at 02:37
  • @MattPhillips: It definitely was interesting enough to attract multiple experts. Did you look at the linked question? The reason there are no competing answers is because things got worked out in the comments there... and some of the comments were getting off-topic for that question and deserved their own Q&A. – Ben Voigt Apr 14 '14 at 02:31
  • @BenVoigt Ok I didn't think the point I was making was really terribly subtle but apparently enough so to derail the commenters here. (How is the linked q. possibly relevant?) I have no problem with self-answered questions; where the asker/answerer is knowledgeable and providing something new, that's great. But a pretense of two people is a little weird, unnecessary, and when you suddenly realize you're reading the OP's own answer, annoying. I tried to improve the answer accordingly, the improvements were rejected, and that's the end of that afaic. – Matt Phillips Apr 14 '14 at 16:03
  • @Matt the linked question is pretty similar. Both dynamically create an object of const type and then ask whether it can be modified. The only difference is how the non const lvalue is obtained. – Ben Voigt Apr 14 '14 at 17:00
  • @MattPhillips: I don't understand your problem. Read almost any FAQ to see the same style. – Lightness Races in Orbit Apr 14 '14 at 17:03