【问题标题】:x86/C++ - Pointer To Pointer: Const being violated by compiler?x86/C++ - 指向指针的指针:编译器违反了常量?
【发布时间】:2015-06-01 21:50:39
【问题描述】:

我正在为我的学生项目的游戏引擎开发一个共享指针(称为 Handle)实现,但我们遇到了一个无法解释的错误。出于某种原因,在我们工厂的某个时间点,有一个无效的内部指针通过句柄传递给工厂,这导致了我们的原型加载过程中的崩溃。我们调试了几个小时,并将任何复杂的语句分解为最简单的版本,以便于调试。我最终将问题归结为 Handle 类的复制构造函数。但是,似乎仍然有一些中间步骤正在释放内部指针。我阅读了所有我能找到的关于什么可能导致这个问题的文章,但没有找到任何东西。最后,我决定看一下反汇编,看看我是否能弄清楚发生了什么。这是没有反汇编的复制构造函数。

template <class TYPE>
Handle<TYPE>::Handle(const Handle<TYPE>& rhs, bool weak) : m_weak(weak), m_ref_count(rhs.m_ref_count), m_ptr(rhs.m_ptr)
{
  if(!m_weak)
    ++(*m_ref_count);
}

这是带有反汇编的复制构造函数。

template <class TYPE>
Handle<TYPE>::Handle(const Handle<TYPE>& rhs, bool weak) : m_weak(weak), m_ref_count(rhs.m_ref_count), m_ptr(rhs.m_ptr)
{
013FFF50 push         ebp
013FFF51 mov          ebp,esp
013FFF53 sub          esp,0CCh
013FFF59 push         ebx
013FFF5A push         esi
013FFF5B  push        edi  
013FFF5C  push        ecx  
013FFF5D  lea         edi,[ebp-0CCh]  
013FFF63  mov         ecx,33h  
013FFF68  mov         eax,0CCCCCCCCh  
013FFF6D  rep stos    dword ptr es:[edi]  
013FFF6F  pop         ecx  
013FFF70  mov         dword ptr [this],ecx  
013FFF73  mov         eax,dword ptr [this]  
013FFF76  mov         cl,byte ptr [weak]  
013FFF79  mov         byte ptr [eax],cl  
013FFF7B  mov         eax,dword ptr [this]  
013FFF7E  mov         ecx,dword ptr [rhs]  
013FFF81  mov         edx,dword ptr [ecx+4]  
013FFF84  mov         dword ptr [eax+4],edx  
013FFF87  mov         eax,dword ptr [this]  
013FFF8A  mov         ecx,dword ptr [rhs]  
013FFF8D  mov         edx,dword ptr [ecx+8]  
013FFF90  mov         dword ptr [eax+8],edx  
  if(!m_weak)
013FFF93  mov         eax,dword ptr [this]  
013FFF96  movzx       ecx,byte ptr [eax]  
013FFF99  test        ecx,ecx  
013FFF9B  jne         Handle<Component>::Handle<Component>+60h (013FFFB0h)  
    ++(*m_ref_count);
013FFF9D  mov         eax,dword ptr [this]  
013FFFA0  mov         ecx,dword ptr [eax+4]  
013FFFA3  mov         edx,dword ptr [ecx]  
013FFFA5  add         edx,1  
013FFFA8  mov         eax,dword ptr [this]  
013FFFAB  mov         ecx,dword ptr [eax+4]  
013FFFAE  mov         dword ptr [ecx],edx  
}

问题(据我所知)在这里:

013FFF6D  rep stos    dword ptr es:[edi]

众所周知,这是用来快速清除内存的。但是,该指令正在清除 rhs 句柄的内部指针指向的指针,不仅如此,它还违反了 const,因为传入的句柄是一个 const 引用。我不确定这是否是 VS 问题,在我们的构建设置中设置的某种标志导致它发生,但复制构造函数可以正常工作,直到我们尝试使用它制作原型。

任何帮助将不胜感激!

编辑 1:在一些 cmets 曝光后,我进一步研究了这个问题,我可能会在我的代码中的某处返回一个临时变量。我找不到任何暗示我这样做的东西,但我确实筛选了更多的反汇编,看看是否能找到任何提示。这是我们的 CreateGenericArchetype 函数,这正是问题出现的地方。

Handle<Object> ObjectFactory::CreateGenericArchetype(const std::wstring &ArchetypeName)
{
  /* Create The Object */
  auto archetype = mComponentFactoryMap.find(std::wstring(L"Object"));
  Handle<Component> created_component = archetype->second->CreateArchetypeComponent();
  Handle<Object> retVal = static_handle_cast<Object>(created_component);
  retVal->mArchetypeName = ArchetypeName;
  mArchetypeMap.insert(std::pair<std::wstring, Handle<Object>>(ArchetypeName, retVal));

  return Handle<Object>(retVal, true);
}

导致我们所有问题的那一行(目前)是这样的:

Handle<Component> created_component = archetype->second->CreateArchetypeComponent();

当然,我也将调试扩展到 CreateArchetypeComponent,所以这里也是:

Handle<Component> ComponentTypeFactory<Object>::CreateArchetypeComponent() const
{
  Object* created_object = new Object;
  Component* casted_object = static_cast<Component*>(created_object);
  Handle<Component> newComponent(casted_object);
  newComponent->mType = mType;
  newComponent->mID = GetNewID();
  return newComponent;
}

Object继承自Component,所以Object到Component的static_cast downcast是有效的,构造函数成功地从Component*构造了Handle。当我们尝试返回我们在函数中构造的句柄时,问题就出现了。这是该交互的反汇编:

return newComponent;
005602AD  push        0  
005602AF  lea         eax,[newComponent]  
005602B2  push        eax  
005602B3  mov         ecx,dword ptr [ebp+8]  
005602B6  call        Handle<Component>::Handle<Component> (04EA050h)  
005602BB  mov         ecx,dword ptr [ebp-110h]  
005602C1  or          ecx,1  
005602C4  mov         dword ptr [ebp-110h],ecx  
005602CA  mov         byte ptr [ebp-4],0  
005602CE  lea         ecx,[newComponent]  
005602D1  call        Handle<Component>::~Handle<Component> (04F2E7Bh)  
005602D6  mov         eax,dword ptr [ebp+8]  
}
00EB02D9  push        edx  
00EB02DA  mov         ecx,ebp  
00EB02DC  push        eax  
00EB02DD  lea         edx,ds:[0EB0318h]  
00EB02E3  call        @_RTC_CheckStackVars@8 (0E4BA9Eh)  
00EB02E8  pop         eax  
00EB02E9  pop         edx  
00EB02EA  mov         ecx,dword ptr [ebp-0Ch]  
00EB02ED  mov         dword ptr fs:[0],ecx  
00EB02F4  pop         ecx  
00EB02F5  pop         edi  
00EB02F6  pop         esi  
00EB02F7  pop         ebx  
00EB02F8  mov         ecx,dword ptr [ebp-10h]  
00EB02FB  xor         ecx,ebp  
00EB02FD  call        @__security_check_cookie@4 (0E4E9BAh)  
00EB0302  add         esp,130h  
00EB0308  cmp         ebp,esp  
00EB030A  call        __RTC_CheckEsp (0E41C3Dh)  
00EB030F  mov         esp,ebp  
00EB0311  pop         ebp  
00EB0312  ret         4  

*******返回 CreateGenericArchetype*******

005C2639  call        __RTC_CheckEsp (04F1C3Dh)  
005C263E  mov         dword ptr [ebp-1D4h],eax  
005C2644  mov         ecx,dword ptr [ebp-1D4h]  
005C264A  mov         dword ptr [ebp-1D8h],ecx  
005C2650  mov         byte ptr [ebp-4],4  
005C2654  mov         edx,dword ptr [ebp-1D8h]  
005C265A  push        edx  
005C265B  lea         ecx,[created_component]  
005C265E  call        Handle<Component>::Handle<Component> (04EA050h)

************从上面复制构造函数反汇编********

005C2663  mov         byte ptr [ebp-4],6  
005C2667  lea         ecx,[ebp-174h]  
005C266D  call        Handle<Component>::~Handle<Component> (04F2E7Bh)  
  Handle<Object> retVal = static_handle_cast<Object>(created_component);

除非使用“created_component”调用我的复制构造函数,因为 rhs 句柄被认为返回了一个局部变量,否则我看不出它可能出错的地方,除非这是因为返回的CreateArchetypeComponent 的句柄在传递给复制构造函数时位于堆栈上,然后被清除。

【问题讨论】:

  • 看起来rep 指令只是清除堆栈并且没有触及任何一个对象。
  • 为那件稀有物品投票:一个体面的、经过充分研究的问题!
  • 我同意这似乎只是用 0xCC 清除您的本地人。这是标准的函数入口点代码,如果它不能正常工作,绝对不会有任何效果。因此,问题出在清除代码中的可能性很小。此外,清除代码的存在只是因为您已通过编译器选项告诉编译器将其放在那里。尝试删除此选项,您应该会得到相同的行为。 (但是,如果您没有得到相同的行为,则可能仍然不是清除代码有问题;您可能正在某处访问未初始化的变量。)
  • 您正在使用/GZ/RTCs 编译器选项编译代码。您的rep stos 操作码使用已知模式 (0xCC) 初始化未初始化的堆栈空间(从 ebp-0xCC 到 ebp)。它不会触及传递给构造函数的任何变量。这些在 ebp 之上传递。
  • 正如其他 cmets 所提到的,rep stos 指令用于清除堆栈的暂存/本地区域(尽管在我看来,该区域在函数的稍后部分使用,但也许这就是非优化调试构建的本质)。如果您传入的对象位于堆栈的那个区域,那么看起来其他一些函数正在返回一个指针或对局部变量的引用。如果您发现是这种情况,那么您可以通过让该函数返回一个对象副本来轻松解决问题。

标签: c++ visual-studio-2013 x86 shared-ptr


【解决方案1】:

感谢您在此问题上提供的所有大力帮助,但我们弄清楚了发生了什么。发生的事情是,在一些 Handle 构造函数中,我们需要为 Handle 的内部指针指向的指针分配内存。所以发生的事情是,Handle 本身正在使用一个保存在堆栈上的指针,当调用 rep stos 操作码时,它正在清除该指针。

【讨论】:

  • 所以你有一个悬空指针指向一个超出范围的自动变量?
  • 有点...被指向的对象是工厂中的新对象,所以它在堆上。然而,Handle 本身使用了一个指向指针的指针,并且持有 Handle 需要指向的地址的数据在堆栈上。当rep stos 执行时,它从堆栈中清除了地址。所以指向指针的指针指向堆栈上的一个位置,而堆栈本身又指向堆上的一个位置。
  • 但是“栈上的位置”(实际上是指针类型的自动变量)已经超出范围了,对吗?
  • 我希望你通过让Handle(T* obj) 构造函数分配一个new T*{obj} 来修复它,当引用计数达到零时它会被删除。顺便说一句,您的Handle 类似乎没有存储足够的元数据来支持static_handle_cast 的正确实现。或者为了支持弱指针,您需要保留所有句柄(包括弱句柄)的计数来决定何时删除元数据,与控制目标生命周期的强句柄的计数分开。
  • 啊,旧的“新项目向导设置调试构建,生成调试信息并使用慢速调试库和发布构建,使用快速库并优化您的代码,因此您无法调试”。我建议您了解项目选项并使用库的发布版本,但在您自己的代码中启用调试信息并且不进行优化。
猜你喜欢
  • 1970-01-01
  • 2020-10-23
  • 2013-01-04
  • 1970-01-01
  • 2019-09-10
  • 2011-08-04
  • 2014-02-23
  • 2021-10-02
  • 2020-04-13
相关资源
最近更新 更多