我有两个类,它们在自己的标头中独立声明,并且它们的方法在自己的 TU/.cpp 中定义。

这些类在名称和命名空间上是相同的,但是存在于代码库中的不同目录中,并且具有不同的功能,它们如下所示:

核心1/abc1.h:

namespace ns {
   class abc {
   ....
      std::map<std::string,int> m;  
   };
}

核心1/abc1.cpp:

    namespace ns {
       abc:~abc() {
       
      };
    }

core2/abc2.h:

namespace ns {
   class abc {
   
      std::unordered_map<std::string,int> m;  
   };
}
  

核心1/abc2.cpp:

namespace ns {
   abc:~abc() {
   
  };
}

当我运行使用这两个类的程序时,当程序结束并且析构函数被调用时,似乎基于来自 abc2.h 类型的实例的gdb ,正在调用 abc1.h 类型的析构函数,然后导致段错误。

使用 g++ 和 clang 时我看到错误,这可能是未定义的行为问题吗?或者可能是链接器错误?(链接器是黄金)


在构建用于链接器的符号名称时,是否会考虑文件名 – 或者只考虑命名空间、类、方法名称这几个因素?

8

  • 3
    “但存在于代码库中的不同目录中” ——这对编译方式完全没有影响。如果你在阅读问题时没有关于目录的这些信息,你能看到问题吗?


    – 


  • 5


    – 

  • 可能是重复的,尽管该问题未使用命名空间,并且未提及文件位置。差异没有影响,所以也许应该将此作为重复关闭?或者应该有一个答案说差异没有影响。


    – 

  • 我更担心这是否是一个链接器问题,它根据名称的混乱情况选择了错误的析构函数,假设析构函数名称最终对于两者是相同的,则链接器最终选择它在内部映射/存储中拥有的第一个。 – @JaMiT 介意将您的评论作为答案,因为它们看起来非常相关。


    – 


  • 3
    由于类在同一个命名空间中具有相同的名称,但它们是不同的,因此违反了 ODR。只需用两个不同的名称命名两个不同的类即可。


    – 


最佳答案
3

这不是一个错误,而是一个 UB,是错误组织项目的结果。

文件名对链接没有影响,只有全 ID 才有影响。标准规定,除具有本地链接的项目外,对变量、函数、类类型、枚举类型或模板有两个定义是未定义的行为。

通常,链接器可以检测到冲突,但可以通过对原始代码进行转换(例如内联或全程序优化)来避免冲突。在链接级别,类的结构不再保留,只有函数代码和 vtable(如果存在)。如果函数调用完全内联,则其名称可能不存在于生成的目标代码文件中的符号表中。假设只有一个析构函数,全程序优化实际上可以做任何事情。

在这种情况下,如果每个版本的类都只有内联成员,并且此类实例永远不会在另一个模块中使用,那么你们可能永远不会遇到错误,并且你可能会遇到非常奇怪的错误,有时只有通过查看汇编代码才能发现其性质。没有名称冲突 – 没有来自链接器的错误。

运行调试器可能会导致您无法链接调试信息或显示执行的错误代码。您应该感到幸运。类似的错误在 vtable 中有所不同,一个类比另一个类具有更多的虚拟函数,我不得不通过比较我所不知道的架构的汇编程序中调用代码所使用的偏移量来进行诊断。

这是“单一定义规则”的结果。如果在链接器阶段,同一个函数(弱定义)有多个实现,则允许链接器任意选择其中任何一个。

解决方法是确保不同的东西有不同的名称(用于区分特征的额外命名空间)。

在构建用于链接器的符号时,是否考虑文件名

不。这与编译器在实现时遇到的困难有关(这是一个非标准的预处理器指令,指示文件应仅处理一次,)。预处理器很容易跟踪哪些文件已通过指令提取#include。使用规范路径跟踪这些文件需要更多努力,但仍然不难(例如,像 这样的路径project/src/../include被简化为project/include)。当操作系统支持符号链接时,就会出现困难。

特定库可以有多个路径。例如,可能有一个库安装在 中/usr/local/boost_1_82_0,其中名称有助于防止库的旧版本干扰新版本。但是,为了方便起见,可能有一个符号链接为 创建/usr/local/include/boost一个别名,/usr/local/boost_1_82_0/boost以便更轻松地使用此库,并且升级库可以变得非常轻松(只需更新符号链接)。

因此,有可能有两个规范路径引用同一个文件。例如/usr/local/boost_1_82_0/boost/bimap.hpp/usr/local/include/boost/bimap.hpp可能引用同一个文件。编译器是否应该因为它们的路径名义上不同而将它们视为不同的文件?不,它们命名同一个文件。(如果单个项目使用这两条路径,则升级库可能会出现问题,但这是另一个问题。)对于实施的人来说#pragma once,这是一个挑战。对于当前的问题,它说明了为什么预处理器不能考虑文件的名称。

以上是从用户的角度进行推理。也有技术原因导致路径未被考虑在内。也许最值得注意的是,处理发生在#include早期阶段,而不是分析语义阶段。这个答案适合那些寻找理由而不是技术细节的人。

或者它只是命名空间、类、方法名称是唯一考虑的因素?

这句话措辞含糊,很难回答“是”或“否”。重要的是完全限定名称(包括命名空间的名称,例如::std::vector或)。方法确实有完全限定名称,在这方面方法名称会被考虑在内。但是,我怀疑其意图是询问在识别::boost::bimaps::bimap时是否考虑方法名称,而在这种情况下,方法名称不会被考虑在内。

类由其完全限定名标识。类的完全限定名包括其标识符和命名空间,但其方法不属于其中。如果同一完全限定名有不同的定义,例如 的两个定义::ns::abc,则违反了

您遇到的是名称冲突,这是命名空间旨在避免的。但是,如果名称冲突是命名空间本身,则此解决方案会失败。​​(看来core1core2不幸选择了相同的命名空间。)