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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With