检测无意的弱链接符号

Detecting unintended weak link symbols

本文关键字:链接 符号 检测      更新时间:2023-10-16

在我们公司,直到最近,我们还没有使用名称空间,因为一些编译器不能很好地支持它们。

这会导致以下错误的大量出现:

file_A.cpp

class Node {
    Data *ptr;
    Node() { ptr = new Data; }
    ~Node() { delete ptr; }
};

file_B.cpp

class Node {
    vector<int> v;
    Point *pt;
    Node(int x,int y) { pt = new Point(x,y); v.push_back(0); }
    ~Node() { delete pt; }
};
void foo() {
    Node n(10,10);
    ...
}    // calls file_B::~Node() !!!

每个作者Node都不知道其他Node的存在,但是因为他期望这个类名可能被重用,所以他没有使用它创建一个.hpp文件。

编译器静默地删除其中一个析构函数,因为它们的签名匹配,并且该错误很难发现,因为它可能不会在不同的计算机上复制。

一旦错误被识别出来,人们就会逐渐意识到它,并试图将定义密封在未命名的名称空间中,或者避免在类体中取消对成员函数的删除。

  • 问题1:既然你不能相信程序员总是记得防御性地编程,那么是否有一种工具可以检测这些"无意的弱链接符号"?

    我所说的意外是指Node类在.hpp文件中没有定义,并且至少有一个类成员在类定义之间不匹配…

  • 问题2:如果我们不使用名称空间,但是我们内联每个函数,是否有可能自动生成的函数(复制-ctor,复制-赋值,析构函数)会产生前面提到的"弱链接错误"?


方法一:将未命名的命名空间

namespace {
  class Node {
     Data *ptr;
     Node() { ptr = new Data; }
     ~Node() { delete ptr; }
  };
}

方法二:避免内联
class Node {
   Data *ptr;
   Node();
   ~Node();
};
Node::Node()  { ptr = new Data; }
Node::~Node() { delete ptr; }

如果您的代码库足够大,可以定制一个现有的编译器来处理您的问题:

  1. LLVM/Clang编译器是可定制的(它是c++的,我不太了解它)。
  2. GCC编译器(最近的版本如4.6)是可扩展的,要么通过用C编码的插件,要么通过用MELT编码的扩展。MELT是一种(免费的,GPLv3许可的)高级领域特定语言,用于扩展GCC。

在这两种情况下,它都是几天或几周的努力,最困难的是部分理解编译器的内部表示(Gimple &

我是MELT的主要作者,我很乐意帮助你使用MELT,所以请随时与我联系。

《c++和链接器》是一本关于这个问题的非常有趣的读物。具体参见"没有强制执行的规则毫无意义"一节。

一种见解是,您可以通过解析目标文件来检测"弱"符号,并查找"W":

$ nm -C foo.o | grep doSomething
00000000 W doSomething()

因此,您可以添加一个后处理步骤,自动收集这些并列出重复的内容。您可以将它们与预期重复项的主列表进行比较,如果有任何新的重复项,则发出标记。

另一个选项可能是gcc的-Fno-weak选项。从文档中还不清楚副本会发生什么,但找出答案可能会很有趣。

链接的文章也回答了你的第二个问题("上述现象"指的是删除重复弱符号的所有实例,只保留一个实例):

在某些情况下编译器必须创建一个符号尽管它内联了函数。例如,当函数指针引用函数。所以,上述现象当启用优化时,它并不总是消失。