我有一些 C++ 代码(由其他人编写)似乎调用了错误的函数。情况是这样的:
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 的类层次结构如下所示:
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);
}
以下内容也相关:
class UTF8InputStreamFromBuffer : public wistringstream {
// foo
};
typedef std::basic_istream<wchar_t> InputStream;
在 Visual C++ 调试器中运行,它显示对 BadDocumentReader 的 readDocument 调用不是在调用
readDocument(InputStream&, const wchar_t*)
反而
readDocument(const LocatedString* source, const wchar_t *, Symbol)
通过在所有 readDocuments 中粘贴 cout 语句可以确认这一点。调用之后,源参数当然充满了垃圾,这很快就会导致崩溃。 locatedString 确实有一个来自 InputStream 的单参数隐式构造函数,但用 cout 检查表明它没有被调用。知道什么可以解释这一点吗?
Edit:其他可能相关的细节:DocumentReader 类与调用代码位于不同的库中。我还对所有代码进行了完全重建,但问题仍然存在。
Edit 2:我使用的是 Visual C++ 2008。
Edit 3:我尝试制作一个具有相同行为的“最小可编译示例”,但无法复制该问题。
Edit 4:
根据 Billy ONeal 的建议,我尝试更改 BadDocumentReader 标头中 readDocument 方法的顺序。果然,当我更改顺序时,它会更改调用哪个函数。在我看来,这证实了我的怀疑,索引到 vtable 时发生了一些奇怪的事情,但我不确定是什么原因造成的。
Edit 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 中。然后它放置firstedx 中 vtable 中的东西并调用它。使用不同顺序的方法重新编译似乎并没有改变这一点。它总是想调用 vtable 中的第一个东西。 (我可能完全误解了这一点,根本不了解汇编......)
感谢您的帮助。
Edit 6:我发现了这个问题,很抱歉浪费了大家的时间。问题在于 GoodDocumentReader 应该被声明为 DocumentReader 的子类,但实际上并非如此。 C 风格的强制转换抑制了编译器错误(应该听你的,@sellibitze,如果你想提交你的评论作为答案,我会将其标记为正确)。棘手的是,该代码纯属意外地运行了几个月,直到有人进行修订,向 GoodDocumentReader 添加了两个虚拟函数,因此它不再靠运气调用正确的函数。