没有调用singleton的构造函数

use*_*932 13 c++ assembly c++11

我有单一类,旨在用于一个线程(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 %p\n", create_context_);
}

void ImageCache::f()
{
    assert(create_context_ == QThread::currentThread());
}
Run Code Online (Sandbox Code Playgroud)

一切正常,但在一台机器上有断言失败ImageCache::f,我没有直接访问该机器(因此这个问题).

有趣的是,根据日志ImageCache::ImageCache 根本没有被调用,断言因为失败而失败

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

我将ImageCache::instance头文件的实现移动到.cpp文件,将更新的源代码发送给这些机器的用户(在我的一切工作正常),他重建并且所有开始按预期工作.

我问他编译二进制文件(断言失败和没有),它们之间的唯一区别是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>
Run Code Online (Sandbox Code Playgroud)

好的是这样的:

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>
Run Code Online (Sandbox Code Playgroud)

不同的是

//bad
mov    0x23dbbb(%rip),%rax 
movzbl (%rax),%eax
//good
movzbl 0x239498(%rip),%eax
Run Code Online (Sandbox Code Playgroud)

我解释这一点,由于某种原因,%eax来自第一个变体的寄存器得到了错误的值,并且由于这个决定了全局对象在未初始化时被初始化.在第二种情况下,所有都按预期工作.

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

另外我用测试代码fprintf(stderr代替qInfo,而用户在第二种情况下看输出而在第一种情况下没有输出.

Phi*_*970 1

原答案

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

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

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

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

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

在 C++ 中,如果不遵循规范,通常会出现未定义的行为。程序员知道他在做什么。

更新

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

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

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

  • C++ 确实保证函数静态变量是唯一的,即使该函数是内联的并且其定义位于多个翻译单元中。尽管我不确定 Windows DLL 链接是否能够在所有情况下正确遵守该规则。 (3认同)