没有调用singleton的构造函数



我有一个singleton类,用于一个线程(GUI线程(,为了防止错误使用,我添加了assert

//header file
class ImageCache final {
public:
ImageCache(const ImageCache &) = delete;
ImageCache &operator=(const ImageCache &) = delete;
static ImageCache &instance()
{
static ImageCache cache;
return cache;
}
void f();
private:
QThread *create_context_ = nullptr;
ImageCache();
};
//cpp
ImageCache::ImageCache()
{
create_context_ = QThread::currentThread();
qInfo("begin, cur thread %pn", create_context_);
}
void ImageCache::f()
{
assert(create_context_ == QThread::currentThread());
}

虽然一切正常,但在一台机器上,ImageCache::f中存在断言失败,我无法直接访问该机器(因此存在此问题(。

有趣的是,根据日志ImageCache::ImageCache根本没有被调用,并且由于而断言失败

assert(0 == QThread::currentThread());

我将CCD_ 5的实现从头文件移动到CCD_,将更新后的源代码发送给这些机器的用户(在我看来一切都很好(,他按照预期重建并全部开工。

我向他询问编译过的二进制文件(有断言失败和没有(,它们之间唯一的区别是ImageCache::instance实现的位置,

并比较汇编程序。

ImageInstance::instance().f()的调用之间没有差异完全并且ImageInstance::instance、的反汇编程序存在一个差异

失败的一个看起来像这样:

static ImageCache &instance()
4938f:   55                      push   %rbp
49390:   48 89 e5                mov    %rsp,%rbp
49393:   41 54                   push   %r12
49395:   53                      push   %rbx
{
static ImageCache cache;
49396:   48 8b 05 bb db 23 00    mov    0x23dbbb(%rip),%rax        # 286f58 <_ZGVZN10ImageCache8instanceEvE5cache@@Base-0x2150>
4939d:   0f b6 00                movzbl (%rax),%eax
493a0:   84 c0                   test   %al,%al
493a2:   0f 94 c0                sete   %al
493a5:   84 c0                   test   %al,%al
493a7:   74 5c                   je     49405 <_ZN10ImageCache8instanceEv+0x76>
493a9:   48 8b 05 a8 db 23 00    mov    0x23dba8(%rip),%rax        # 286f58 <_ZGVZN10ImageCache8instanceEvE5cache@@Base-0x2150>
493b0:   48 89 c7                mov    %rax,%rdi
493b3:   e8 08 b7 fe ff          callq  34ac0 <__cxa_guard_acquire@plt>

好的是这样的:

ImageCache &ImageCache::instance()
{
50c12:   55                      push   %rbp
50c13:   48 89 e5                mov    %rsp,%rbp
50c16:   41 54                   push   %r12
50c18:   53                      push   %rbx
static ImageCache cache;
50c19:   0f b6 05 98 94 23 00    movzbl 0x239498(%rip),%eax        # 28a0b8 <_ZGVZN10ImageCache8instanceEvE5cache>
50c20:   84 c0                   test   %al,%al
50c22:   0f 94 c0                sete   %al
50c25:   84 c0                   test   %al,%al
50c27:   74 50                   je     50c79 <_ZN10ImageCache8instanceEv+0x67>
50c29:   48 8d 3d 88 94 23 00    lea    0x239488(%rip),%rdi        # 28a0b8 <_ZGVZN10ImageCache8instanceEvE5cache>
50c30:   e8 cb 3d fe ff          callq  34a00 <__cxa_guard_acquire@plt>

区别在于

//bad
mov    0x23dbbb(%rip),%rax 
movzbl (%rax),%eax
//good
movzbl 0x239498(%rip),%eax

我解释说,由于某种原因,第一个变量的%eax寄存器得到了错误的值,因此决定全局对象被初始化,而它没有被初始化。在第二种情况下,一切如预期。

那么是编译器故障(gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 / amd64 / linux(还是由于某种原因我应该在.cpp内部使用ImageCache::实例,或者其他导致差异代码生成的原因,比如某些编译器错误可能导致此失败?代码是用-O0 -std=c++11和其他一些标志编译的,这些标志是cmake在编译依赖于Qt库的共享库时自动添加的。

我还询问了使用CCD_ 14而不是CCD_,并且用户在第二种情况下看到输出,而在第一种情况下没有输出。

原始答案

据我所知,在头文件中使用该函数的问题是,您可以获得多个定义,然后行为是未指定的。

从本质上讲,编译器可能会生成多个函数instance,每个编译单元都有一个函数,其中包含该标头,因此,如果在链接时不合并/消除这些函数,则每个函数都有自己的变量。

在windows中,如果我们在多个DLL中编译相同的代码,其中一些变量在每个动态库中重复,我们可能会遇到类似的问题。

然后会发生的情况是,因为每个客户端都有自己的副本,所以一个客户端所做的更改不会被另一个翻译单元(你的问题(或另一个DLL(我的问题(中的另一个客户端看到。

通过将定义移动到源文件,您将获得单个定义,从而避免问题。

在C++中,如果不遵守规范,通常会得到未定义的行为。这取决于程序员知道他在做什么。

更新

正如所指出的,在一篇评论中,根据目前的标准,我的假设可能是错误的。因此,问题也可能是过时的编译器编译器错误

正在发生的事情的可能解释:

在许多情况下,当编译器合并重复项时,代码将是相同的,因此选择哪一个不会有任何区别。这里,假设编译器为静态变量分配2个不同的地址(每个编译单元一个(,并以某种方式内联对instance()的调用,即使用原始变量而不是合并(选择(的变量,它可以解释观察到的行为。

看起来QThread::currentThread()有问题。它使用了一个模块本地静态对象——我想。如果是这样的话,模块与用户代码的链接顺序会导致行为差异。你试过不同版本的QT吗?我想这个版本的Qt有一个过时的设计——没有使用现代习语,比如Meyer singleton,甚至没有使用古老的漂亮计数器技巧。

最新更新