未定义的行为或优化错误?

时间:2021-05-19 20:03:51

标签: c++ visual-c++ compiler-optimization undefined-behavior

最近我发现我正在处理的代码如果在具有速度优化的发布模式下编译会出现错误行为。我花了一段时间将整个代码库缩减为一个文件,例如保留行为。如果将 x64 编译为 C++17 并通过 VC++ 2019 14.28.29910 进行速度优化,这会变得很明显(至少在我的系统上)。没有速度优化,C++14x86 按预期工作。还有很多看似无关的事情会影响错误的结果。这就是为什么您可以在以下示例中看到各种虚拟构造的原因,只是为了保留错误。当您编译此代码时,如果编译器按预期将其放置在特定条件下并将其放入垃圾中,则每个新放置元素的 left 和 top 应该是 20, 20 - 否则。此条件是std::vector的容量足以无需重新分配就位。

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
#include <algorithm>

void do_nothing(int) {}

struct Point { 
    float x, y; 
};

struct Size { 
    float width, height; 
};

struct Rect { 
    float left, top, right, bottom; 
};

Point origin(Rect const& rc) { 
    return { rc.left, rc.top }; 
}

Size size(Rect const& rc) { 
    return { rc.right - rc.left, rc.bottom - rc.top }; 
}

class DummyBase {
public:
    virtual void bf0() {} 
    std::vector<DummyBase*> others;
};

class Elem : public DummyBase
{
public:
    Elem(Elem& parent, Rect const& rc)
    {
        interconnect_with_parent(parent);
        _rc = rc;
    }

    Elem(Elem const& other) = delete;

    Elem& operator=(Elem const& other) = delete;

    Elem(Elem&& other) noexcept :
        DummyBase   ( std::move(other) ),
        _rc         { std::move(other._rc) }
    {       
        interconnect_with_parent(*other._parent);
        _interconnect_with_chn(std::move(other.chn));       
    }

    ~Elem() { 
        // this example is not intended for children that is not a part of their parent
        if (chn.size()) throw;
        _parent->_disconnect_child(*this); 
    }

    void interconnect_with_parent(Elem& new_parent)
    { 
        if (&new_parent != _parent) {
            _connect_parent(new_parent);
            _parent->_connect_child(*this);
        }
    }

    Rect const& rc() const {
        return _rc;
    }

    Point origin() const { 
        return ::origin(_rc); 
    }

    Size size() const { 
        return ::size(_rc); 
    }

    Elem const& parent() const {
        return *_parent;
    }

    void move(Size delta)
    { 
        _rc.left   += delta.width;
        _rc.right  += delta.width;
        _rc.top    += delta.height;
        _rc.bottom += delta.height;

        for (size_t i = 0; i < chn.size(); i++)
            chn[i]->move(delta);
    }

    void move_on_x_to(float x) {
        move(Size{ x - rc().left, 0 });
    }

    void move_on_y_to(float y) {
        move(Size{ 0, y - rc().top });
    }

    void resize(Size delta)                     
    { 
        _rc.right  += delta.width;
        _rc.bottom += delta.height;
    }

    void resize_right_to(float x) {
        resize(Size{ x - rc().right, 0 });
    }

    void resize_bottom_to(float y) {
        resize(Size{ 0, y - rc().bottom });
    }

    void print(std::string str0 = "", bool print_parent = false) const
    {
        if (not str0.empty()) 
            std::cout << str0 << ":\n";

        std::cout <<
            std::setw(col0_w) << std::left << type_name() <<
            " address: " << static_cast<const void*>(this) <<
            "\tl: " << std::setw(12) <<  _rc.left   << 
            " r: " << std::setw(12) <<  _rc.right  << 
            " t: " << std::setw(12) <<  _rc.top    << 
            " b: " << std::setw(12) <<  _rc.bottom << "\n";

        if (print_parent) {
            std::string str = " parent: ";
            std::cout << str;
            col0_w -= str.size();
            parent().print("", false);
            col0_w += str.size(); 
        }
    }

    void print_all(int sep_depth = -1, bool print_parent = false) const
    {
        auto str = type_name() + "'s tree from leaves:\n";
        std::cout << str << std::string(str.size(), '-') << "\n";
        _on_descendants(
            [print_parent](Elem& desc){ desc.print("", print_parent); },
            [sep_depth](int depth){ if (depth == 1) std::cout << "\n"; }
        );
        print("", print_parent);
    }

protected:
    void count_print_col0_w() const
    {
        col0_w = 0;
        _on_descendants([](Elem& desc){  
            size_t size = desc.type_name().size();
            if (size > col0_w) col0_w = size; 
        });
        col0_w += 10;
    }

private:
    void _interconnect_with_chn(std::vector<Elem*>&& _chn)
    { 
        chn = std::move(_chn);
        for (size_t i = 0; i < chn.size(); i++)
            chn[i]->_connect_parent(*this);
    }

    void _connect_parent(Elem& new_parent)
    {
        if (_parent) _parent->_disconnect_child(*this);  
        _parent = &new_parent; 
        main = &parent() != this ? new_parent.main : this; 
        _on_descendants([this](Elem& descendant)
        { 
            descendant.main = this->main; 
        });
    }

    void _connect_child(Elem& child)                
    { 
        if (&child != this)
            chn.push_back(&child); 
    }

    void _disconnect_child(Elem& child)             
    { 
        auto pos = std::find(chn.begin(), chn.end(), &child);
        if (pos != chn.end()) chn.erase(pos);
    }

    // act_on_desc(Elem&), act_on_depth(int)
    template<class F0, class F1 = void(int)>
    void _on_descendants(F0 act_on_desc, F1 act_on_depth = do_nothing) const
    {
        static int depth = -1;
        for (size_t i = 0; i < chn.size(); i++) {
            depth++;
            chn[i]->_on_descendants(act_on_desc, act_on_depth); 
            act_on_desc(*chn[i]), act_on_depth(depth);
            depth--;
        }
    }

    std::string type_name() const
    {
        auto type_name = std::string(typeid(*this).name());
        while (true) {
            std::string substr("class ");
            auto pos = type_name.find(substr);
            if (pos != std::string::npos) type_name.erase(pos, substr.size());
            else break;
        }
        return type_name;
    }

    Rect _rc{};
    std::vector<Elem*> chn;
    Elem* _parent{ nullptr };
    Elem* main{ nullptr };
    static size_t col0_w;
};

size_t Elem::col0_w = 50;

class PointElem : public Elem {
public:
    PointElem(Elem& parent, Point point) : 
        Elem{ parent, Rect{ point.x, point.y, point.x, point.y } } {}
};

template<class ElemT>
class DummyWrapper : public ElemT {
public:
    using ElemT::ElemT;
private:
    Elem dummy_child{ *this, ElemT::rc() };
};

// aligns, resizes and orders elements on emplace
template<class ElemT>
class ElemVec : public Elem
{
public:
    ElemVec(Elem& parent, Point origin, float right) : 
        Elem( parent, Rect{ origin.x, origin.y, right, origin.y } )
    { 
        dummy_call();
    }

    template<class...Args> 
    ElemT& emplace(Args&&...args)
    {
        auto& elem = elements.emplace_back(std::forward<Args>(args)...);
        // emplaced AnotherElem's left is not what was provided
        // in Main::emplace_another_elem (that is ElemVec's left), but garbage
        // if no reallocation has occurred on emplacing
        elem.print("emplaced elem");
        adapt(elem), order_elements();
        return elem;
    }

    void adapt(Elem& elem)
    {
        if (&elem.parent() != this)
            elem.interconnect_with_parent(*this);
        elem.move_on_x_to(rc().left);
        elem.resize_right_to(rc().right);
    }

    void order_elements() 
    {
        if (not elements.empty()) 
        {
            float next_top = rc().top;
            auto iter = elements.end() - 1;
            while (true) {
                iter->move_on_y_to(next_top);
                if (iter == elements.begin()) break;
                next_top = iter->rc().bottom;
                iter--;
            }
            resize_bottom_to(iter->rc().bottom);
        }
    }

    void dummy_call() {
        if (elements.size()) 
            order_elements(), throw;
    }

    std::vector<ElemT> elements;
};

class AnotherElem_ : public Elem {
public:
    AnotherElem_(Elem& parent, Point origin, float right) : 
        Elem{ parent, Rect{ origin.x, origin.y, right, origin.y + 20 } },
        pt_elem{ std::make_unique<PointElem>(*this, this->origin()) } {}
private:
    std::unique_ptr<PointElem> pt_elem;
};

using AnotherElem = DummyWrapper<AnotherElem_>;

class Main : public Elem
{
public:
    Main(Rect const& rc) : Elem{ *this, rc } 
    {
        count_print_col0_w();
    }

    void emplace_another_elem()
    {
        size_t prev_capacity = elemvec->elements.capacity();

        float right = elemvec->rc().right;
        AnotherElem& emplaced = elemvec->emplace(*elemvec, elemvec->origin(), right);

        size_t capacity = elemvec->elements.capacity();
        if (capacity != prev_capacity) std::cout << "REALLOCATION has occurred\n";
        else std::cout << "NO REALLOCATION has occurred\n";
        emplaced.print("emplaced elem after adapting and reordering");
        std::cout << "\n";
    }

private:
    std::unique_ptr<ElemVec<AnotherElem>> make_elem_vec()
    {
        return std::make_unique<ElemVec<AnotherElem>>(*this, Point{ rc().left + 20, rc().top + 20 }, rc().right - 20);
    }

    std::unique_ptr<ElemVec<AnotherElem>> elemvec{ make_elem_vec() };
};

int main()
{ 
    std::cout.precision(5);

    Main m(Rect{ 0, 0, 300, 300 });
    for (size_t i = 0; i < 10; i++) {
        std::cout << "i:" << i << "\n";
        m.emplace_another_elem();
    }
    
    // just notice it has a side effect in printing order of ElemVec elements 
    // it prints in order of reconnection, but I think it probably doesn't matter for this example
    // m.print_all(2, true);

    std::cin.get();
    return 0;
}

我已经生成了一个程序集列表。它看起来像是在没有重新分配编译器放置原点的堆栈内存的分支上

movsd QWORD PTR $T4[rsp], xmm1 

被覆盖

mov QWORD PTR $T18[rsp], rbx

可能在调用 _Mylast 构造函数之前使用 std::vectorAnotherElem 指针。 $T4$T4$T18 是相同的偏移量。

_TEXT   SEGMENT
$T32 = 48
$T18 = 48
$T3 = 48
$T4 = 48
$T24 = 56
$T35 = 56
$T22 = 88
$T33 = 88
right$ = 88
__$ArrayPad$ = 96
this$ = 144

?emplace_another_elem@Main@@QEAAXXZ PROC        ; Main::emplace_another_elem, COMDAT

; 
; ... omission ...
; 

; 313  :  float right = elemvec->rc().right;

    movss   xmm3, DWORD PTR [rdi+40]
    movss   DWORD PTR right$[rsp], xmm3

; 24   :  return { rc.left, rc.top }; 

    movss   xmm0, DWORD PTR [rdi+36]

; 77   :  return ::origin(_rc); 

    movss   xmm1, DWORD PTR [rdi+32]
    unpcklps xmm1, xmm0
    movsd   QWORD PTR $T4[rsp], xmm1

; 711  :  if (_Mylast != _My_data._Myend) {

    mov rbx, QWORD PTR [rdi+96]
    cmp rbx, QWORD PTR [rdi+104]
    je  SHORT $LN16@emplace_an

; 695  :  static void construct(_Alloc&, _Objty* const _Ptr, _Types&&... _Args) {

    mov QWORD PTR $T18[rsp], rbx
    mov rax, QWORD PTR $T3[rsp]
    mov QWORD PTR $T22[rsp], rax
    mov r8, rax
    mov rdx, rdi
    mov rcx, rbx
    call    ??0AnotherElem_@@QEAA@AEAVElem@@UPoint@@M@Z ; AnotherElem_::AnotherElem_
    npad    1
    lea rax, OFFSET FLAT:??_7?$DummyWrapper@VAnotherElem_@@@@6B@
    mov QWORD PTR [rbx], rax
    lea rcx, QWORD PTR [rbx+96]
    lea r8, QWORD PTR [rbx+32]
    mov rdx, rbx
    call    ??0Elem@@QEAA@AEAV0@AEBURect@@@Z    ; Elem::Elem
    npad    1

如果它很快就会被覆盖,为什么编译器会把它放在那里。对我来说似乎不是优化。如果您在代码中指出 UB 的可能原因,那就太好了。

编辑:修复了一些在 VS2019 中没有出现的错误

0 个答案:

没有答案
相关问题