Нет вызова конструктора синглтона - PullRequest
0 голосов
/ 19 декабря 2018

У меня есть одноэлементный класс, предназначенный для использования в одном потоке (поток с графическим интерфейсом), чтобы он был защищен от неправильного использования. Я добавляю 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 из-за

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>

хороший яs выглядит так:

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 :: instance внутри .cpp по какой-то причине или по какой-то другой причине, которая вызывает генерацию разностного кодаКак некоторые ошибки компилятора могут вызвать этот сбой?Код был скомпилирован с -O0 -std=c++11 и некоторыми другими флагами, которые cmake добавляет автоматически при компиляции разделяемой библиотеки с зависимостью от библиотеки Qt.

Также я прошу тестовый код с использованием fprintf(stderr вместо qInfo, ипользователь видит вывод во втором случае и не выводит в первом случае.

Ответы [ 2 ]

0 голосов
/ 13 марта 2019

Похоже, проблема с QThread::currentThread().Он использует модуль локального статического объекта - наверное.И если это так, порядок связей модуля с кодом пользователя приводит к поведенческим различиям.Вы пробовали другую версию QT?Я полагаю, что эта версия Qt имеет устаревший дизайн - не использует современные идиомы, такие как синглтон Мейера или даже старый хитрый встречный трюк.

0 голосов
/ 19 декабря 2018

Оригинальный ответ

Насколько я понимаю, проблема с функцией в заголовочном файле заключается в том, что вы можете получить несколько определений, а затем поведение не определено.

По сути, компилятор может генерировать несколько функций instance, по одной для каждой единицы компиляции, которая включает этот заголовок, и, таким образом, если они не будут объединены / исключены во время соединения, у каждой будет своя собственная переменная.

В Windows у нас может быть похожая проблема, если мы скомпилируем один и тот же код в нескольких библиотеках DLL, где некоторые переменные дублируются в каждой динамической библиотеке.

Тогда что произойдет, потому что каждый клиент имеет свою собственную копию, изменениесделанный одним не виден другим клиентом в другом модуле перевода (ваша проблема) или другой DLL (моя проблема).

Переместив определение в исходный файл, вы получите одно определение и, таким образом, избежитепроблема.

В C ++, если вы не следуете спецификации, вы часто получаете неопределенный behavior.Программист должен знать, что он делает.

Обновление

Как указано, в комментарии моё предположение может быть неверным в соответствии с действующим стандартом.Таким образом, проблема также может быть в устаревшем компиляторе или ошибке компилятора .

Возможное объяснение того, что происходит:

Во многих случаях, когдакомпилятор объединяет дубликаты, код будет идентичен, поэтому он не будет иметь никакого значения, какой из них выбран.Здесь, если предположить, что компилятор назначит 2 разных адреса для статической переменной (по одному для каждой единицы компиляции) и каким-то образом встроить вызов instance() таким образом, чтобы он использовал исходную переменную вместо объединенного (выбранного), он мог быобъясните наблюдаемое поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...