Potential C++ compiler optimization with/without throw/noexcept function
Asked Answered
V

2

10

Assume the following class:

class Example
{
public:
...
    Example& operator=(const Example& rhs);
...
private:
    other_type *m_content;
    size_t m_content_size;
}

Example& Example::operator=(const Example& rhs)
{
    if (this != &rhs)
    {
        delete m_content;
        m_content = nullptr;
        m_content = getCopiedContent(rhs);
    }

    return *this;
}

I know that this is not the best way to implement operator= but that's on purpose, because my question is about these two lines:

    m_content = nullptr;
    m_content = getCopiedContent(rhs);

Can be that the compiler will optimize out m_content = nullptr; even though getCopiedContent is not defined as throw() or noexcept:

other_type* getCopiedContent(const Example& obj);

On one hand the compiler may assume that if right after m_content = nullptr; I overwrite the value of m_content with the return value of getCopiedContent, it may optimize out the whole m_content = nullptr; expression. On the other hand if the compiler optimizes it out and getCopiedContent throws an exception, m_content will contain a non valid value.

Does C++ standard state anything regarding such scenario?

Vermis answered 9/11, 2015 at 13:36 Comment(1)
Long story short: You may assume your code behaves exactly as it would without any optimization. The only exceptions to this are RVO and UB.Lindsey
C
5

Can be that the compiler will optimize out m_content = nullptr; even though getCopiedContent is not defined as throw() or noexcept:

Yes. This is a redundant operation with no side-effects. Any self-respecting compiler will optimise the redundant store away. In fact you'll have to work really hard to keep the redundant store from being optimised out, such as:

  1. make it std::atomic (if it's atomic, writes are obliged to to transmitted to other threads)
  2. make it volatile
  3. surround the write with some kind of memory barrier (e.g. lock a std::mutex) for the same reasons as (1)

On the other hand if the compiler optimizes it out and getCopiedContent throws an exception, m_content will contain a non valid value

Good observation. The compiler is permitted to perform the write of nullptr in the exception handler. i.e. it may re-order instructions in order to save operations provided the total outcome was 'as if' it did not.

Does C++ standard states anything regarding such scenario?

Yes. It has the 'as-if' rule. While reasoning about one thread, the visible outcome must be 'as-if' each of your statements were executed sequentially with no optimisations against a non-pipelined, non-cached, very simple memory model. Note that no computer produced in the past 20 years is actually this simple, but the outcome of the program must be as if it were.

There is one exception to this - copy elision. Side effects of eliding redundant copies under certain circumstances do not need to be preserved. For example, while eliding copies of arguments that are temporaries and during RVO.

Cointreau answered 9/11, 2015 at 13:50 Comment(8)
"Yes. This is a redundant operation with no side-effects." No it's not, OP explained why it isn't. If getCopiedContent throws one can observe whether or not it was done, so it must be done.Lindsey
that scenario was covered in the second part of the narrative.Cointreau
Then why did you write "Yes" as the first word in your answer? That is clearly wrong. At least write "It may be optimized to be done only getCopiedContent actually does not throw." or something like this, the part about doing it in the exception handler would be fine afaics.Lindsey
Forgive me, but the question was "Can be that the compiler will optimize out m_content = nullptr; even though getCopiedContent is not defined as throw() or noexcept:". While there are some caveats, the answer is "yes". As ever, the obligation is on the compiler to prove that there will be no observable side effects. If (for example) it has sight of the code in getCopiedContent it can prove that whether or not the function is declared to be exception-free.Cointreau
So if I understood your answer correctly, the compiler may perform some reordering which will optimize the main line (e.i. moving the m_content = nullptr; to an exception handler) but it is guaranteed that m_content will not contain a non valid value. Did I get it correct? If so, where exactly in the exception handler will it place m_content = nullptr;? What if the relevant try-catch is in a different compilation unit?Vermis
But in general, the compiler cannot prove that an arbitrary function is noexcept, and in this general case, the assignment may not be optimized away. It may be moved elsewhere (like in the exception handler), but it will not be removed. That contradicts a simple "Yes. This is a redundant operation with no side-effects.".Lindsey
@RichardHodges, there is a forth way to force redundant store - call any opaque function which potentially can have access to this variable in between two stores. In our case, this would be a member function of the class, defined in a different unit of translation.Ambrogino
True. The compiler is required to be conservative in the case of aliasingCointreau
R
0

I believe this is called Dead Store Elimination.

I don't know if compiler optimizations are included in the standards except for the as-if rule.

Here is the code for Clang for the dead store elimination. http://www.llvm.org/docs/doxygen/html/DeadStoreElimination_8cpp_source.html This will only do block local ones.

Maybe there are some tools that can inline the function and do the analysis as block local to see if that nullptr store can be eliminated. Obviously, a throw in the function somewhere would make the analysis keep the nullptr store.

Obviously, the scenario you described violates the as-if rule so that is not standard compliant.

Recant answered 9/11, 2015 at 15:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.