9

Look at this little snippet:

struct A {
    virtual ~A() { }
};

struct B { };

bool fn() {
    A *volatile a = new A;
    return dynamic_cast<B *>(a); 
}

Is the compiler allowed to remove the dynamic_cast altogether, and transform dynamic_cast to a simple nullptr;?

The reason of this question is this answer.

Notes:

  • Assume that volatile means that the compiler cannot assume anything about a, because it's volatile. Here's a question why.

  • The fact that dynamic_cast may not be allowed to be removed is that there could be a type somewhere in the program, which derives from both A and B.

geza
  • 28,403
  • 6
  • 61
  • 135
  • 4
    Are you aware of the [as-if rule](https://stackoverflow.com/questions/15718262/what-exactly-is-the-as-if-rule)? – NathanOliver Sep 27 '18 at 21:41
  • Note that making `a` volatile here doesn't matter, because the compiler can see that nobody else has the ability to touch `a`. You might try declaring `a` at namespace scope instead. – Brian Bi Sep 27 '18 at 21:48
  • @Brian: there was some discussion about this here in SO, with no real conclusion. If you think this way, then please threat `a` as if it were in namespace scope (compilers usually respect volatile, and doesn't try optimize around its value, even if it is a local variable). `a` could come from a function, which the compiler doesn't know anything about. – geza Sep 27 '18 at 21:56
  • 1
    @NathanOliver: yes, but understanding the as-if rule doesn't help answering this question. Suppose that I'm a compiler writer. Am I allowed to create a compiler which omits the dynamic_cast or not here? – geza Sep 27 '18 at 21:56
  • I'm actually a little surprised that this compiles without at least warnings (which it does) - the compiler can easily see that B is not derived from A. –  Sep 27 '18 at 22:06
  • 1
    @NeilButterworth: there could be a type somewhere, which derives from `A` and `B` as well. – geza Sep 27 '18 at 22:07
  • 2
    @geza wow, I haven't seen this one coming. You should put it in the question. It's important to the analysis and it's not obvious. – bolov Sep 27 '18 at 22:10
  • Another reason why volatile automatic variables don't make much sense – M.M Sep 27 '18 at 22:12
  • @Brian "_the compiler can see that nobody else has the ability to touch_" It cannot, by definition of `volatile` – curiousguy Oct 02 '18 at 22:18
  • @curiousguy That's not the definition of `volatile`. The definition of `volatile` from [intro.abstract] is that accesses to `volatile` variables are observable behaviour. The compiler certainly can determine that a volatile automatic variable's address doesn't leave the scope, and therefore that nobody else can touch it. – Brian Bi Oct 03 '18 at 17:02
  • 1
    @Brian: there are opinions that observable behavior has a wider definition. Like observing with a debugger. Or, there is a possibility, that a program using debugging information, inspects itself, and even modifies a local variable from a signal handler. See: https://stackoverflow.com/questions/51472394/is-it-allowed-for-a-compiler-to-optimize-away-a-local-volatile-variable – geza Oct 03 '18 at 17:09
  • @Brian "_volatile automatic variable's address doesn't leave the scope, and therefore that nobody else can touch it_" That would also apply to a static volatile variable if you analyse the whole TU, or a global volatile variable if you analyse the whole program. Hopefully, the compiler is not allowed to reason about volatile variables. – curiousguy Oct 03 '18 at 21:09

2 Answers2

2

Yes, a compiler can omit the call to dynamic_cast as per as-if rule 1) if and only if it can prove that the only valid result of the call is false. That's simple.

The tricky part is to prove that the only valid result of dynamic_cast is false. You can prove that iff there is no class in your whole program that inherits both from A and B.

Now I am not very versed in this part, but I think you can do that when you create the binary and have all the types in your program only if it's an executable (not a library) and only if the program doesn't dynamically link to other libraries.


1) the dynamic_cast on pointers doesn't have side effect, it doesn't throw

bolov
  • 72,283
  • 15
  • 145
  • 224
  • The optimization here would be not because there's no class relating them, but because the actual most derived type is known from the new-expression. Except for the quirk about whether the `volatile` means that the pointer `a` could change to point to something else between its initialization and the `dynamic_cast`. – aschepler Sep 27 '18 at 22:34
  • 1
    @aschepler I don't understand your comment. Yes I consider `volatile` in my analysis. It's in the question. – bolov Sep 27 '18 at 23:05
  • "only if the program doesn't dynamically link to other libraries." - that isn't covered by the standard anyway – M.M Sep 28 '18 at 01:20
  • @M.M no, but it's a reality – bolov Sep 28 '18 at 01:23
  • @bolov in reality I doubt there is any possible way that reading the value of `a` could give an unexpected result , it seems to me we are on theoretical ground here – M.M Sep 28 '18 at 01:24
  • 1
    @M.M. It shouldn't be too hard to come up with an example where a `volatile` pointer *can* change out of line, e.g. involving [DMA](https://en.wikipedia.org/wiki/Direct_memory_access). But I agree that the question is impossible to answer. A better question would be: "Under which circumstances is this optimization allowed?" – Arne Vogel Sep 28 '18 at 10:18
2

Yes. dynamic_cast has no observable behavior beyond its return value.

The compiler is aware of the static type pointed to by a.

So under the as-if rule, the compiler is free to evaluate the dynamic cast at compile time.

In fact:

struct A { virtual ~A() {} };
struct B:A {};

bool foo() {
  A* a = new A;
  return dynamic_cast<B*>(a);
}

the above dynamic cast statement can also be optimized to return false; The new cannot be omitted without whole program optimization as someone could overload the global operator new; once it is proven that no global operator new is overloaded, it could even optimize out the call to new A, as neither allocating memory by the default operator new, nor creating an A nor destroying one has any observable side effects.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1) you mean `return true;` and 2) I was under the impression that the compiler can always elide heap allocations even if they have side effects (the operators themselves that is). http://eel.is/c++draft/expr.new#10 – Rakete1111 Sep 27 '18 at 22:29
  • 1
    "The compiler is aware of the static type pointed to by a". But it is volatile, shouldn't the compiler ignore anything it knows about the value of `a`? – geza Sep 27 '18 at 23:32
  • 1
    @geza What volatile means is so ambuguous that it is next to meaningless. Usually implementations do something with it. – Yakk - Adam Nevraumont Sep 28 '18 at 01:34