C++调用对象的完全错误(虚拟)方法

Rya*_*ard 15 c++ virtual-functions overloading

我有一些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);
Run Code Online (Sandbox Code Playgroud)

条件为真的文件处理正常; 它是虚假崩溃的那些.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);
}
Run Code Online (Sandbox Code Playgroud)

以下也是相关的:

class UTF8InputStreamFromBuffer : public wistringstream {
    // foo
};
typedef std::basic_istream<wchar_t> InputStream;
Run Code Online (Sandbox Code Playgroud)

在Visual C++调试器中运行,它显示BadDocumentReader上的readDocument调用没有调用

readDocument(InputStream&, const wchar_t*)
Run Code Online (Sandbox Code Playgroud)

反而

readDocument(const LocatedString* source, const wchar_t *, Symbol)
Run Code Online (Sandbox Code Playgroud)

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

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

编辑2:我正在使用Visual C++ 2008.

编辑3:我尝试使用相同的行为制作"最低限度可编辑的示例",但无法复制问题.

编辑4:

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

编辑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  
Run Code Online (Sandbox Code Playgroud)

我不太了解汇编,但它看起来像是它取消引用读者变量指针.存储在这部分内存中的第一件事应该是指向vtable的指针,因此它将其解释为eax.然后它将第一个东西放在edx中的vtable中并调用它.使用不同的方法顺序重新编译似乎并没有改变这一点.它总是想要在vtable中调用第一件事.(我完全可能误解了这一点,根本不了解装配......)

谢谢你的帮助.

编辑6:我发现了问题,我为浪费每个人的时间而道歉.问题是GoodDocumentReader应该被声明为DocumentReader的子类,但事实上并非如此.C风格的演员阵容抑制了编译错误(应该听你的,@ sellibitze,如果你想提交你的评论作为答案,我会把它标记为正确).棘手的是,代码已经运行了几个月纯粹的意外,直到有人向GoodDocumentReader添加了两个虚函数,因此不再通过运气调用正确的函数.

Ada*_*eld 13

发生这种情况是因为不同的源文件在类的vtable布局上不一致.调用函数的代码认为readDocument(InputStream &, const wchar_t *)是在特定的偏移量,而实际的vtable具有不同的偏移量.

这通常发生在更改vtable时,例如通过在该类或其任何父类中添加或删除虚方法,然后重新编译一个源文件而不是另一个源文件.然后,您将获得不兼容的目标文件,当您链接它们时,事情就会变得繁荣.

要解决此问题,请完全清理并重建所有代码:库代码和使用库的代码.如果您没有库的源代码,但是您确实拥有类定义的头文件,那么这不是一个选项.在这种情况下,您无法修改类定义 - 您应该将其恢复为给定的方式并重新编译所有代码.


Mat*_* M. 3

我会先尝试删除 C 型铸件。

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

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

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

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
Run Code Online (Sandbox Code Playgroud)