C++ 调用对象的完全错误(虚拟)方法 [英] C++ calling completely wrong (virtual) method of an object

查看:67
本文介绍了C++ 调用对象的完全错误(虚拟)方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些 C++ 代码(由其他人编写)似乎调用了错误的函数.情况如下:

I have some C++ code (written by someone else) which appears to be calling the wrong function. Here's the situation:

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 的类层次结构如下所示:

The files for which the condition is true are processed fine; the ones for which it is false crash. The class hierarchy for DocumentReader looks like this:

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 调用不是调用

Running in a Visual C++ debugger, it shows that the readDocument call on a BadDocumentReader is calling not

readDocument(InputStream&, const wchar_t*)

而是

readDocument(const LocatedString* source, const wchar_t *, Symbol)

这是通过在所有 readDocuments 中粘贴 cout 语句来确认的.在调用之后,源参数当然充满了垃圾,这很快就会导致崩溃.LocationString 确实有一个来自 InputStream 的单参数隐式构造函数,但检查 cout 显示它没有被调用.知道什么可以解释这个吗?

This is confirmed by sticking cout statements in all the readDocuments. After the call the source argument is of course full of garbage, which shortly causes a crash. LocatedString does have a one-argument implicit constructor from InputStream, but checking with a cout shows it's not getting called. Any idea what could explain this?

编辑:其他可能相关的详细信息:DocumentReader 类与调用代码位于不同的库中.我还对所有代码进行了完整的重建,但问题仍然存在.

Edit: other possibly relevant details: The DocumentReader classes are in a different library than the calling code. I also have done a complete rebuild of all the code and the problem remained.

编辑 2:我使用的是 Visual C++ 2008.

Edit 2: I'm using Visual C++ 2008.

编辑 3:我尝试制作一个具有相同行为的最小可编译示例",但无法复制该问题.

Edit 3: I tried making a "minimally compilable example" with the same behavior, but was unable to replicate the problem.

编辑 4:

在 Billy ONeal 的建议下,我尝试更改 BadDocumentReader 标头中 readDocument 方法的顺序.果然,当我改变顺序时,它会改变被调用的函数.在我看来,这证实了我的怀疑,索引到 vtable 时发生了一些奇怪的事情,但我不确定是什么原因造成的.

At Billy ONeal's suggestion, I tried changing the order of the readDocument methods in the BadDocumentReader header. Sure enough, when I change the order, it changes which of the functions gets called. This seems to me to confirm my suspicion there's something weird going on with indexing into the vtable, but I'm not sure what's causing it.

编辑 5:这是函数调用前几行的反汇编:

Edit 5: Here's the disassembly for the few lines before the function call:

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 中.然后它将 first 放在 edx 的 vtable 中并调用它.使用不同顺序的方法重新编译似乎不会改变这一点.它总是想调用 vtable 中的第一件事.(我可能完全误解了这一点,根本没有组装知识......)

I don't know much assembly, but it looks to me like it's dereferencing the reader variable pointer. The first thing stored in this part of memory should be the pointer to the vtable, so it dereferences that into eax. Then it places the first thing in the vtable in edx and calls it. Recompiling with different orders of the methods doesn't seem to change this. It always wants to call the first thing in the vtable. (I could have completely misunderstood this, having no knowledge of assembly at all...)

感谢您的帮助.

编辑 6: 我发现了问题,对于浪费大家的时间,我深表歉意.问题是 GoodDocumentReader 应该被声明为 DocumentReader 的子类,但实际上不是.C 风格的强制转换抑制了编译器错误(应该听你的,@sellibitze,如果你想提交你的评论作为答案,我会把它标记为正确的).棘手的是,该代码纯粹是偶然地工作了几个月,直到有人在 GoodDocumentReader 中添加了另外两个虚函数,因此它不再靠运气调用正确的函数时进行了修订.

Edit 6: I found the problem, and I apologize for wasting everyone's time. The problem was that GoodDocumentReader was supposed to be declared as a subclass of DocumentReader, but in fact was not. The C-style casts were suppressing the compiler error (should have listened to you, @sellibitze, If you'd like to submit your comment as an answer, I'll mark it as correct). The tricky thing is that the code had been working for several months by pure accident until a revision when someone added two more virtual functions to GoodDocumentReader so it was no longer calling the right function by luck.

推荐答案

我会先尝试删除 C-cast.

I would try removing the C-cast first.

  • 完全没有必要,从派生到基类的转换在语言中很自然
  • 实际上,它可能会导致错误(尽管不应该)

它看起来像是一个编译器错误……它肯定不是 VS 中的第一个.

It looks like a compiler bug... it would certainly not be the first either in VS.

不幸的是,我手头没有 VS 2008,在 gcc 中,转换正确发生:

I unfortunately don't have VS 2008 at hand, in gcc the casts occur correctly:

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 << "\n";

  return 0;
}


> Derived: 0x7ffff1377e00, Base1: 0x7ffff1377e00, Base2: 0x7ffff1377e08

这篇关于C++ 调用对象的完全错误(虚拟)方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆