在虚拟函数周围使用#ifdef预处理器会导致与库链接的程序出现运行时错误[关闭]
Posted
技术标签:
【中文标题】在虚拟函数周围使用#ifdef预处理器会导致与库链接的程序出现运行时错误[关闭]【英文标题】:Using #ifdef preprocessor around virtual functions causes runtime error in program linked against libraries [closed] 【发布时间】:2021-02-12 00:33:54 【问题描述】:我试图在虚拟函数周围使用#ifdef 预处理器。代码的简化版本如下所示:
class Base
#ifdef ENABLE_FLAG
virtual void function1();
#endif //ENABLE_FLAG
virtual void function2();
virtual void function3();
;
class Child : public Base
#ifdef ENABLE_FLAG
void function1() override;
#endif //ENABLE_FLAG
void function2() override;
void function3() override;
;
代码编译良好。但是,当我的应用程序调用 Child::function3() 时,实际上由于某种原因最终调用了 Child::function2()。我认为预处理器以某种方式弄乱了虚拟表。
我在 Visual Studio 2017 中运行调试模式。我很好奇这个运行时问题的原因是什么。这是依赖于编译器的行为吗?
要注意的另一件有趣的事情是,如果我确保定义了 ENABLE_FLAG 并删除 Child 类中的 #ifdef 子句并将其保留在 Base 类中,编译器实际上会引发编译错误。它在这里有什么不同?
更新:这个类在主程序和库中都使用。
【问题讨论】:
如果您链接的对象(或库)并非全部使用相同的ENABLE_FLAG
设置(已定义或未定义)编译,则会发生这种情况。
如果它在一个头文件中,有时包含在定义的ENABLE_FLAG
中,有时没有,这违反了单一定义规则(标题版本)。
显然它并不一致,无论出于何种原因。
@JohnYang 如果您更改是否定义了 ENABLE_FLAG,CMake 会重建一切吗?如果没有,那就去吧。
你应该确保你的标志是可见的。一种方法是使用 use a pragma 生成警告或错误,以便您可以确认它按预期工作(编译文件并使用预期值)。很多事情都可能出错,因为符号可以在许多位置定义并且可能可见或不可见。
【参考方案1】:
虚拟函数列在每个类的表中 - virtual function table
或 vtable
- 类的每个实例都包含一个指向该表的指针,该表本身包含指向该类的每个虚拟函数的指针,按照它们在类声明中列出的顺序 (*)。 (如果你想看的话,有很多描述虚拟表的教程文章和视频。)
因此,如果您在启用#ifdef
的情况下编译程序的部分,而在启用#ifdef
的情况下编译程序的另一部分 不启用 - 不同部分会看到不同数量的虚拟功能,并且 vtables 将不同。这不应该发生,并导致您看到的问题。
所以不要那样做。你违反了一个非常严格的 C++ 规则,称为 ODR
或 "One Definition Rule"。如果你这样做,一切都悬而未决 - 即,任何事情都可能出错。
(关于 ODR 的奇怪之处:对于如此严格的关键规则,编译器(即整个编译系统)不需要以任何方式告诉您您违反了它。所以,真的,不要。
顺便说一句,所有库和主程序必须看到相同的类声明!上面关于 ODR 的那些东西? C++ 标准不知道“库”——静态、动态或其他。对于它,只有程序和编译单元。 (这些“库”的东西只是编译系统为我们提供的一种便利。)所以所有的规则都适用于整个程序——而 ODR 是最常咬你的那个! (正如你刚刚发现的......)
(*) 初步近似,不包括多重继承...
【讨论】:
这回答了我相信的第二个问题。对于我的第一个问题,我在 CMake 文件中编译开始时加载的宏中定义了 ENABLE_FLAG。这仍然会导致问题吗? ENABLE_FLAG 应该在整个应用程序中保持一致。 @JohnYang - 显然它并不一致。您可能应该在您的构建系统中检查它。您是否将其构建到库和链接到库的程序中,并使用不同的标志设置?如果您进行干净的构建,它仍然会发生吗? 是的,我正在将它构建为一个由另一个主应用程序链接的库。那么您是否建议我必须使用相同的 ENABLE_FLAG 设置来设置我的主应用程序?进行更改时,我总是进行干净的构建。 @JohnYang - 绝对是的 - 库和主程序必须看到相同的类声明!我对 ODR 的回答中的那些东西? C++ 标准不知道“库”——静态、动态或其他。对于它,只有程序和编译单元。 (这些“库”的东西只是编译系统为我们提供的一种便利。)所以所有的规则都适用于整个程序——而 ODR 是最常咬你的那个!正如你刚刚发现的那样......以上是关于在虚拟函数周围使用#ifdef预处理器会导致与库链接的程序出现运行时错误[关闭]的主要内容,如果未能解决你的问题,请参考以下文章