Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No call of constructor of singleton

I have singleton class, intended to for use in one thread (GUI thread), to protected from wrong usage I add 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());
}

all works fine, but on one machine there is assertion failure in ImageCache::f, I have no direct access to that machine (hence this question).

The interesting thing, that according to log ImageCache::ImageCache was not called at all, and assert failed because of

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

I move implementation of ImageCache::instance from header file to .cpp file, send updated source code to user of these machine (on my all works fine), he rebuilds and all start works as expected.

I ask him for compiled binaries (with assert failure and without) the only difference between them is place of ImageCache::instance implementation,

and compare assembler.

there is no difference between calls of ImageInstance::instance().f() at all, and there is one difference in disassembler of ImageInstance::instance,

the failure one looks like this:

 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>

the good one is looks like this:

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>

The difference is

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

I interpret this, that for some reason %eax register from the first one variant got wrong value, and because of this got decision that global object was initialized while it is not initialized. In the second case all works as expected.

So is it compiler failure (gcc (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0 / amd64 / linux) or I should use ImageCache::instance inside .cpp for some reason, or some other reason that cause difference code generation, like some compiler falgs may cause this failure? Code was compiled with -O0 -std=c++11 and some other flags that cmake add automatically while compile shared library with dependency on Qt library.

Also I ask test code with usage of fprintf(stderr instead of qInfo, and the user see output in the second case and no output in the first case.

like image 626
user1244932 Avatar asked Dec 18 '18 22:12

user1244932


1 Answers

Original answer

As far as I understand, the problem having the function in the header file is that you can get multiple definitions and then the behavior is unspecified.

Essentially, the compiler might generate multiple functions instance, one for each compilation unit that include that header and as such if they are not merge/eliminated at link time, each one would have their own variable.

In windows, we can have similar problem if we compile the same code in multiple DLLs where some variables get duplicated in each dynamic library.

Then what would happen is that because each client has their own copy, a change made by one is not seen by another client in another translation unit (your problem) or another DLL (my problem).

By moving the definition to the source file, you will get a single definition and thus avoid the problem.

In C++, if you don't follow the specification, you often get undefined behavior. It is up to the programmer to know what he is doing.

Update

As pointed out, in a comment my assumption might be wrong according to current standard. Thus the problem might also be an outdated compiler or a compiler bug.

Possible explanation of what is happening:

In many cases, when the compiler merge duplicates, the code would be identical so it would not make any difference which one is selected. Here, assuming that the compiler assign 2 different addresses for the static variable (one for each compilation unit) and somehow inline the call to instance() in a way that it used the original variable instead of the merged (selected) one, it might explain the observed behavior.

like image 98
Phil1970 Avatar answered Sep 19 '22 14:09

Phil1970