C++ calling completely wrong (virtual) method of an object
我有一些C++代码(别人写的),它似乎在调用错误的函数。情况如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | UTF8InputStreamFromBuffer* cstream = foo(); wstring fn = L"foo"; DocumentReader* reader; if (a_condition_true_for_some_files_false_for_others) { reader = (DocumentReader*) _new GoodDocumentReader(); } else { reader = (DocumentReader*) _new BadDocumentReader(); } // the crash happens inside the following call // when a BadDocumentReader is used doc = reader->readDocument(*cstream, fn); |
条件为真的文件处理得很好;条件为假的文件崩溃。DocumentReader的类层次结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class GenericDocumentReader { virtual Document* readDocument(InputStream &strm, const wchar_t * filename) = 0; } class DocumentReader : public GenericDocumentReader { virtual Document* readDocument(InputStream &strm, const wchar_t * filename) { // some stuff } }; class GoodDocumentReader : public DocumentReader { Document* readDocument(InputStream & strm, const wchar_t * filename); } class BadDocumentReader : public DocumentReader { virtual Document* readDocument(InputStream &stream, const wchar_t * filename); virtual Document* readDocument(const LocatedString *source, const wchar_t * filename); virtual Document* readDocument(const LocatedString *source, const wchar_t * filename, Symbol inputType); } |
以下也是相关的:
1 2 3 4 | class UTF8InputStreamFromBuffer : public wistringstream { // foo }; typedef std::basic_istream<wchar_t> InputStream; |
在VisualC++调试程序中运行,它显示了BADDCONTRONTER读取器上的RealDebug调用不调用
1 | readDocument(InputStream&, const wchar_t*) |
而是
1 | readDocument(const LocatedString* source, const wchar_t *, Symbol) |
这是通过在所有的readdocuments中粘贴cout语句来确认的。调用之后,source参数当然充满了垃圾,这很快就会导致崩溃。locatedstring没有一个来自inputstream的单参数隐式构造函数,但使用cout进行检查表明它没有被调用。知道什么可以解释这个吗?
编辑:其他可能相关的细节:documentreader类与调用代码位于不同的库中。我还完成了所有代码的完整重建,问题仍然存在。
编辑2:我使用Visual C++ 2008。
编辑3:我尝试用相同的行为制作一个"最低可编译的示例",但无法复制问题。
编辑4:
在BillyOneal的建议下,我尝试更改baddocumentreader头中readdocument方法的顺序。当然,当我更改顺序时,它会更改调用哪个函数。在我看来,这证实了我的怀疑,在vtable中索引有一些奇怪的事情发生,但我不确定是什么引起的。
编辑5:以下是函数调用前几行的反汇编:
1 2 3 4 5 | 00559728 mov edx,dword ptr [reader] 0055972E mov eax,dword ptr [edx] 00559730 mov ecx,dword ptr [reader] 00559736 mov edx,dword ptr [eax] 00559738 call edx |
我不太了解程序集,但在我看来,它好像在取消对读卡器变量指针的引用。存储在这部分内存中的第一件事应该是指向vtable的指针,因此它将把它解引用到eax中。然后它将第一个东西放在EDX的vtable中并调用它。用不同顺序的方法重新编译似乎并没有改变这一点。它总是想调用vtable中的第一件事。(我可能完全误解了这一点,完全不了解集会……)
谢谢你的帮助。
编辑6:我发现了问题,很抱歉浪费了大家的时间。问题是gooddocumentreader应该声明为documentreader的一个子类,但实际上不是。C样式的强制转换抑制了编译器错误(应该已经听过您的,@sellibitze,如果您想提交您的评论作为答案,我会将其标记为正确的)。棘手的是,代码仅仅是意外地工作了几个月,直到有人在gooddocumentreader中添加了两个虚拟函数,所以它不再幸运地调用正确的函数。
这是因为不同的源文件在类的vtable布局上不一致。调用函数的代码认为
这通常发生在更改vtable时,例如通过在该类或其任何父类中添加或删除虚拟方法,然后重新编译一个源文件而不是另一个源文件。然后,你会得到不兼容的对象文件,当你链接它们的时候,事情会很快发展起来。
要解决这个问题,请对所有代码进行完全清理和重建:库代码和使用库的代码。如果您没有库的源代码,但是您有类定义的头文件,那么这不是一个选项。在这种情况下,您不能修改类定义——您应该将它恢复为它是如何提供给您的,并重新编译所有代码。
我会先尝试移除C型铸件。
- 这是完全不必要的,从派生到基的强制转换在语言中是自然的。
- 实际上,它可能会导致一个错误(尽管不应该如此)
看起来像是个编译器错误…这肯定不是第一次在vs.
不幸的是,我手头没有与2008年相比,在海湾合作委员会的铸型正确发生:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | struct Base1 { virtual void foo() {} }; struct Base2 { virtual void bar() {} }; struct Derived: Base1, Base2 { }; int main(int argc, char* argv[]) { Derived d; Base1* b1 = (Base1*) &d; Base2* b2 = (Base2*) &d; std::cout <<"Derived:" << &d <<", Base1:" << b1 <<", Base2:" << b2 <<" "; return 0; } > Derived: 0x7ffff1377e00, Base1: 0x7ffff1377e00, Base2: 0x7ffff1377e08 |
我有这个问题,问题是我将它存储在一个类成员变量中。当我将它更改为指针并涉及new/delete时,它成功地注册了子类及其函数。
基于程序集,似乎非常清楚绑定是动态的,并且从vtable的第一个条目开始。问题是哪个虚拟表!?!我建议您使用
有一种可能性并不完全符合ASM,但仍然值得一提。因为您在调用