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

2024-03-28

我有一些 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 添加了两个虚拟函数,因此它不再靠运气调用正确的函数。


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

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • VIM:有没有一种简单的方法可以从 Vim 管理 Visual Studio 解决方案/makefile 项目? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我尝试使用 Visual Studio 而不是 VIM 插件 但说实话 VS 相对于 VIM 的唯一优势是它能够自动管理我的项目 我知道 VS
  • pthread_create 编译返回错误

    我使用以下代码创建两个线程 header files include
  • 使 minGW 控制台程序在没有控制台的情况下运行

    我在 MinGW 中制作了一个控制台程序 它执行网络和文件操作 然而 它是一个控制台程序 如何让它安静地运行 没有控制台 没有窗口 尝试这个 ShowWindow GetConsoleWindowHandle SW HIDE GetCons
  • 为什么 strcat() 之后字符串会被改变?

    这是源代码 int main char str dance char str1 hello char str2 abcd strcat str1 str2 printf s str output bcd why str更改后strcat s
  • 解决方案将无法构建,因为它无法从服务生成的类型转换为我的类型

    我有一个 WCF 服务项目 它构建得很好 可以生成可访问的 WSDL 并且 svcutil exe 不会生成任何错误 我有一个访问该 Web 服务的 服务管理器 项目 并且我已成功向其中添加了服务引用 ABCService 第三个项目包含我
  • 有没有办法在 .Net 中创建“自托管”网站? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 类似于 WCF 自托管 有没有办法创建一个在控制台应用程序中 自托管 的网站或dll 提出这个问题的方
  • 将 void* 作为函数调用而不声明函数指针

    我已经搜索过 但找不到任何结果 我的术语可能有问题 所以如果以前有人问过这个问题 请原谅我 我想知道是否有一种简单的方法可以调用void 作为 C 中的函数 无需首先声明函数指针 然后为函数指针分配地址 IE 假设要调用的函数是类型void
  • 调试Windows服务

    Scenario 我有一个用 C 编写的 Windows 服务 我已经阅读了所有关于如何调试它的谷歌线程 但我仍然无法让它工作 我已经运行 PathTo NetFramework InstallUtil exe C MyService ex
  • 初始化影子变量

    标准中是否有任何内容定义从它隐藏的变量初始化变量 例如 int i 7 int i i Visual Studio 2013 允许这样做而不发出警告并按预期工作 内在i变量是 7 然而 Clang 和 GCC 给我一个警告 关于从自身初始化
  • 访问二维数组的一行末尾之后的元素是否是 UB?

    以下程序的行为是否未定义 include
  • 阅读 C 语言中的科学记数法

    我正在尝试读取包含以下内容的文件 1 0000000e 01 2 9265380e 03 5 0821200e 02 4 3231640e 01 2 0000000e 01 1 0170240e 04 9 2798610e 02 4 072
  • C# 检查闰年

    我想将输入日期 1 年添加到名为完成日期的列中 如果输入日期是闰年 我需要添加 364 天 如果不是 365 天的话 有没有办法在 c 中检查这一点 使用当前日期时间年份并操作闰年 不操作 然后添加天数 Thanks 您可以使用日期时间 I
  • 无法使用 process.ErrorDataReceived c# 获取进程错误输出

    我已经建立了Form我使用了一段时间的应用程序 现在我想捕捉StandardError我的流程及其standartOutput 我查看了答案SO and MSDN https msdn microsoft com en us library
  • 不区分大小写“包含(字符串)”

    有没有办法让下面的返回为真 string title ASTRINGTOTEST title Contains string 似乎没有过载允许我设置区分大小写 目前我将它们都大写 但这很愚蠢 我指的是i18n http en wikiped
  • 可变长度数组性能影响 (C/C++)

    我正在编写一个相当简单的函数 它将数组发送到文件描述符 但是 为了发送数据 我需要附加一个一字节标头 这是我正在做的事情的简化版本 它似乎有效 void SendData uint8 t buffer size t length uint8
  • 缓冲区溢出(与)缓冲区溢出(与)堆栈溢出[重复]

    这个问题在这里已经有答案了 可能的重复 堆栈溢出和缓冲区溢出有什么区别 https stackoverflow com questions 1120575 what is the difference between a stack ove
  • LINQ 表达式树 Any() 位于Where() 内

    我正在尝试生成以下 LINQ 查询 Query the database for all AdAccountAlerts that haven t had notifications sent out Then get the entity
  • 如何在 if () 语句中声明变量? [复制]

    这个问题在这里已经有答案了 可能的重复 在 C 的条件或控制语句中声明和初始化变量 https stackoverflow com questions 1516919 declaring and initializing a variabl
  • 引用计数类和多线程

    我是多线程编程的新手 对此我仍然感到困惑 下面是我的引用计数类 class Rbuffer private char m pnData volatile unsigned int mRefCount public Rbuffer int n
  • Phong 着色问题

    我正在根据以下内容编写着色器冯模型 http en wikipedia org wiki Phong reflection model 我正在尝试实现这个方程 其中 n 是法线 l 是光线方向 v 是相机方向 r 是光反射 维基百科文章中更

随机推荐