Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could the compiler have optimized the dynamic_cast away?

Tags:

c++

I have stepped onto code like this:

dynamic_cast<A*>(p)->foo();

which is of course terrible, since when dynamic_cast returns 0, you have an undefined behavior.

Then I thought, one terrible surprise that it could bring, is that since when p can be cast to a A* it is the same as doing a static_cast, and when it cannot you get undefined behavior, the compiler could change the dynamic_cast into a static_cast and keep a conforming behavior. That said, I tried the following code with compiler explorer:

class A {
    virtual void bar() = 0;
};

class B final: public A {
    public:
    void foo();
    void bar() override;
};

void h();

void f(A* const p)
{
    dynamic_cast<B*>(p)->foo();
}

and to my surprise every compiler kept the dynamic_cast call. Is there something that eluded me or do compilers simply do not do a possible optimization?

like image 743
guillaume guigue Avatar asked Jan 25 '23 15:01

guillaume guigue


1 Answers

I don't see why the optimisation wouldn't be possible. The compiler can see that only two cases exist:

  • The dynamic type of the operand is B, so the cast can be static
  • The dynamic type of the operand is some other type deriving from A, in which case the result is nullptr and the full expression has undefined behaviour

(No types deriving from B are possible, due to the final specifier. This is convenient because, if they were possible, a static cast would not necessarily be an adequate substitution for the dynamic cast — multiple inheritance and sidecasts would have to be considered, and there may not be sufficient information in this translation unit to do so. Link-time optimisation would further mitigate that, but in practice the majority of optimisations like this happen at compile-time, and any platform supporting things like dlopen would also veto the possibility.)

So, we have only one case that results in a well-defined program.

Since compilers are permitted to assume that the input does not have undefined behaviour, if removing all code paths that result in UB leaves you with just one possible outcome, then the compiler can just assume that'll always be the outcome. That's the core reason for undefined behaviour to be a thing, to permit optimisations like this, and compilers perform them all the time to make your code nice and fast.

I'll admit to being slightly surprised that the mainstream compilers don't make use of that opportunity in this case. I'd at least expect a warning about a redundant dynamic_cast; still, it's possible that some static analysers give you this.

like image 123
Lightness Races in Orbit Avatar answered Feb 04 '23 16:02

Lightness Races in Orbit