问题描述
最近我发现我正在处理的代码如果在具有速度优化的发布模式下编译会出现错误行为。我花了一段时间将整个代码库缩减为一个文件,例如保留行为。如果将 x64
编译为 C++17
并通过 VC++ 2019
14.28.29910
进行速度优化,这会变得很明显(至少在我的系统上)。没有速度优化,C++14
、x86
按预期工作。还有很多看似无关的事情会影响错误的结果。这就是为什么您可以在以下示例中看到各种虚拟构造的原因,只是为了保留错误。当您编译此代码时,如果编译器按预期将其放置在特定条件下并将其放入垃圾中,则每个新放置元素的 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,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,float right) :
Elem{ parent,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,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::vector
的 AnotherElem
指针。 $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 中没有出现的错误
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)