我有单例类,用于在一个线程(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());
}所有操作都很好,但是在一台机器上,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>好的地方是这样的:
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)还是我应该使用ImageCache::.cpp中的实例是因为某种原因,或者是其他一些导致不同代码生成的原因,比如一些编译器falgs可能会导致这个失败?代码是用-O0 -std=c++11和其他一些标志编译的,cmake在编译具有Qt库依赖性的共享库时会自动添加。
另外,我要求使用fprintf(stderr而不是qInfo的测试代码,用户看到第二种情况下的输出,在第一种情况下没有输出。
发布于 2018-12-19 01:09:28
原始答案
据我所知,头文件中函数的问题是,您可以获得多个定义,而行为却未指定。
本质上,编译器可能会生成多个函数( instance ),每个编译单元都有一个包含该标头的函数,因此,如果它们在链接时没有合并/消除,那么每个函数都会有自己的变量。
在windows中,如果在多个DLL中编译相同的代码,在每个动态库中重复一些变量,我们可能会遇到类似的问题。
然后会发生的情况是,由于每个客户端都有自己的副本,另一个客户端在另一个翻译单元(您的问题)或另一个DLL (我的问题)中没有看到由一个客户所做的更改。
通过将定义移动到源文件,您将得到一个单独的定义,从而避免这个问题。
在C++中,如果不遵循规范,通常会得到未定义的行为。这取决于程序员知道他在做什么。
更新
正如所指出的,根据目前的标准,我的假设可能是错误的。因此,问题还可能是过时的编译器或编译器错误。
对正在发生的情况的可能解释:
在许多情况下,当编译器合并重复项时,代码将是相同的,因此它将不会对所选择的代码产生任何影响。在这里,假设编译器为静态变量分配两个不同的地址(每个编译单元一个),并且以某种方式内联对instance()的调用,使其使用原始变量而不是合并的变量,这可能会解释所观察到的行为。
发布于 2019-03-13 19:19:31
看起来是QThread::currentThread()的一个问题。它使用的是一个模块本地静态对象-我猜。如果是这样的话,模块与用户代码的链接顺序会导致行为差异。你试过不同版本的QT吗?我想这个版本的Qt有一个过时的设计--不使用现代成语,比如,甚至旧的漂亮的计数器技巧。
https://stackoverflow.com/questions/53841757
复制相似问题