虚拟方法混乱,如何找出原因?

3
我的一位同事今天遇到了一些C++代码的问题。他正在调试一个对象的虚拟方法的奇怪行为。每当该方法执行时(在调试下,Visual Studio 2005),一切都会出错,调试器不会进入该方法,而是进入对象的析构函数!此外,对象的虚拟表只列出了它的析构函数,没有其他方法。
我以前从未见过这种行为,运行时错误会打印出有关ESP寄存器的信息。我希望我可以给你正确的错误消息,但我现在记不清了。
无论如何,你们中的任何人都遇到过这种情况吗?是什么原因导致了这种行为?如何解决?我们尝试多次重建项目,重新启动IDE,但都没有帮助。我们还在该方法调用之前使用了_CrtCheckMemory函数来确保内存处于良好状态,并返回true(表示正常)。我已经没有更多的想法了。你呢?

涉及多个线程吗? - Seth
我曾经有一个想法,但现在我不再正确地记得它了。如果人类的记忆有像Windows剪贴板这样的东西,将事物轻松复制到其中以供以后使用,那该多好啊。 - anon
不太可能是罪魁祸首 - 但无论如何,请确保您不会从构造函数/析构函数中调用任何虚方法。 - Danra
我不是。之前就学过那个教训 :) - Geo
你有检查错误之前发生了什么吗?如果那抹掉了ESP,你可能会得到一个延迟的错误。 - Mark Ransom
显示剩余2条评论
5个回答

2

我以前见过这种情况。通常是因为我在Debug模式下使用了Release版本的.LIB文件中的类。可能有其他人已经看到更好的例子,我会把我的答案让给他们。


我不确定这是否正确。我们以调试模式重新构建了整个东西。 - Geo
重新启动您的计算机并进行干净的重建? - wheaties
我明天早上尝试一下。 - Geo
没有帮助。我们确保没有从配置中使用其他内容。仍然没有任何进展。 - Geo
1
我会看一下其他人建议了什么。尤其是Greg的答案。 - wheaties

1
也许您在需要使用static_cast<>的地方使用了C风格的转换?这可能会导致您报告的那种错误,特别是当涉及到多重继承时,例如:
class Base1 {};
class Base2 {};
class Derived : public Base1, public Base2 {};

Derived *d = new Derived;
Base2* b2_1 = (Base2*)d; // wrong!
Base2* b2_2 = static_cast<Base2*>(d); // correct
assert( b2_1 == b2_2 ); // assertion may fail, because b2_1 != b2_2

请注意,这并不总是适用的情况,这取决于编译器和涉及的所有类的声明(当所有类都有虚方法时可能发生,但我手头没有确切的规则)。
或者,你的代码中完全不同的部分出现了问题,并且正在覆写内存。尝试隔离错误并检查是否仍然发生。`CrtCheckMemory`只能找到一些覆写内存的情况(例如当你写入特别标记的堆管理位置时)。

我同意使用static_cast而不是C风格的转换,但是在你的代码中使用C风格的转换并没有本质上的错误 - 它可以很好地处理继承。 - sbk
不,C风格的转换是错误的,这里有更详细的解释:http://en.wikipedia.org/wiki/Virtual_method_table#Multiple_inheritance_and_thunks - 有趣的是,但偶然的是,这个例子看起来很像我的 =) - Frunsi
好吧,至少VS2008编译器没有修复指针。尽管另一篇维基百科文章建议使用C风格的转换来解决这个问题:http://en.wikipedia.org/wiki/Thunk#Thunks_in_object-oriented_programming - 我很困惑,要么维基百科是错的,要么这是VS2008编译器的一个错误。 - Frunsi

1
如果您使用错误数量的参数调用函数,这很容易破坏您的堆栈并产生未定义的行为。我记得在使用MFC时,某些错误可能会导致这种情况发生,例如如果您使用分派宏将消息指向一个没有正确数量或类型参数的方法(我记得那些函数指针没有强类型检查)。自从我上次遇到这个特定的问题以来已经过去了大约十年,所以我的记忆有点模糊。

+1 为提及 MFC 在参数数量不匹配时的奇怪行为。 - Mark Ransom

1
ESP 的值在函数调用之间没有被正确保存。
这种行为通常表明调用代码与创建特定类的代码使用了不同的类或函数定义。
有可能加载的是一个不同版本的组件 dll,而不是最新构建的版本?如果您在后期构建步骤中复制了某些内容,或者进程从不同的目录运行或在执行 LoadLibrary 或等效操作之前更改了其 dll 搜索路径,则可能会发生这种情况。
我经常在复杂项目中遇到这种情况,其中类定义被更改以添加、删除或更改虚函数的签名,然后进行增量构建,并且并非所有需要重新编译的代码实际上都被重新编译。理论上,如果程序的某个部分正在覆盖某些多态对象的 vptr 或 vtable,那么它可能会发生,但我始终发现错误的部分构建是更可能的原因。
这可能是“用户错误”,即开发人员故意告诉编译器仅构建一个项目,而其他项目应该重新构建,或者具有多个解决方案或解决方案中的多个项目,其中依赖关系未正确设置。
非常偶尔,即使解决方案中的项目正确链接,Visual Studio 有时也可能出现生成的依赖关系不正确的情况。这种情况发生的频率比人们指责 Visual Studio 的次数要少。
清除所有中间构建文件并从源代码重新构建所有内容通常可以解决这个问题。显然,对于非常大的项目来说,这可能是一个严重的惩罚。

1

既然这只是猜测,那我也来说一个:

你的堆栈出了问题,而_CrtCheckMemory并不会检查这个问题。至于为什么堆栈会被破坏:

  • 老掉牙的堆栈溢出
  • 调用约定不匹配,这已经被提到过了(我不知道,比如将回调以错误的调用约定传递给WinAPI函数;你链接了哪些静态或动态库?)
  • printf("%d");这样的代码行

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接