我正在使用xlC
在AIX上编译Cppcheck。每个检查器类都派生自Check
类,其构造函数负责在全局静态列表中注册该类检查器。
以下是相关代码的相关部分(文件名链接到Github上的完整源代码):
class Check {
public:
Check() {
instances().push_back(this);
instances().sort();
}
static std::list<Check *> &instances() {
static std::list<Check *> _instances;
return _instances;
}
// ...
};
class CheckBufferOverrun: public Check {
// ...
};
// Register this check class (by creating a static instance of it)
namespace
{
CheckBufferOverrun instance;
}
注意如何在 header 文件中的_instances
函数内声明static
静态变量(没有相应的check.cpp
文件)。使用g++
进行编译时,编译器和链接器协同工作以确保静态instances()
函数只有一个实现,因此只有静态_instances
列表的一个实例。在不同.cpp
文件中实例化的所有不同检查器类都会在同一个_instances
列表中注册。
但是,在AIX的xlC
下,相同的代码最终会为包含它的每个instances()
文件创建一个不同的 .cpp
函数。具有不同的静态_instances
列表。因此,不再存在单个中心_instances
列表,这会导致Cppcheck无法运行大部分检查。
在这种情况下哪个编译器的行为是正确的?
更新:这个问题不是关于如何修复问题,我已经这样做了。我很好奇哪种行为正确。
答案 0 :(得分:9)
g ++具有正确的行为:应该只有一个对象实例。 C ++标准说(C ++ 03 7.1.2 / 4):
extern内联函数中的静态局部变量始终引用同一个对象。
因为类有外部链接,静态成员函数也有外部链接,按照C ++ 03 3.5 / 5:
成员函数...如果类的名称具有外部链接,则具有外部链接。
因为成员函数是在类定义中定义的,所以它是一个内联函数,根据C ++ 03 7.1.2 / 3:
在类定义中定义的函数是内联函数。
答案 1 :(得分:2)
在这种特殊情况下,g ++是正确的(假设instances()
静态成员函数返回一个引用,这是一个错字,对吧?)。您可能希望将instances()
函数的定义移动到cpp文件,这应该是在xlc中的解决方法。
答案 2 :(得分:1)
纠正于:
static std::list<Check *>& instances();
// check.cpp
std::list<Check *> & Check::instances()
{
static std::list<Check *> _instances;
return _instances;
}
如果在头文件中内联函数,则每个编译单元最初将编译自己的定义。但是,在链接时,由于 One-Definition Rule,它们应该缩减为一个实例。
我确信您知道,Check的构造函数不是线程安全的。你的单例构造函数也不是完全线程安全的,因为虽然ODR确保它们之间的线程只能创建一个实例,但list的构造函数不是原子的,因此我不确定它是否保证在另一个线程看到之前完全构造它
对于构建单例或非原子静态的完全线程安全的方法,可以使用boost :: once。