LINUX.ORG.RU

Проверка на знание документации

 ,


1

2

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

Каждый пользователь Qt очень часто в своей жизни сталкивается с подобным кодом:

int main( int argc, char** argv)
{
    QApplication app( argc, argv);
    int result = app.exec();
    return result;
}

Этот тривиальный пример стал настолько близким и родным каждому, кто пишет код на C++/Qt, что вопросов по его работе не возникает ни у кого. Но давайте попробуем окунуться немного глубже и рассмотрим вот такой пример:

QApplication* createApplication( int argc, char** argv)
{
    QApplication* result = new QApplication( argc, argv);
    return result;
}

int main( int argc, char** argv)
{
    QApplication* app = createApplication( argc, argv);
    int result = app->exec();
    delete app;
    return result;
}

Прошу вас, не пытайтесь повторить этот трюк в реальных приложениях. В особенности, если от них зависит жизнь и здоровье людей (хотя бы разработчиков), т.к. этот код приводит к undefined behavivor.

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

Итак, ждем нашего победителя.

---

Наш победитель: slovazap

Осторожно, ниже в треде есть ответы. Если вы хотите сами найти решение - не читайте тред до конца.

★★★★★

Последнее исправление: trex6 (всего исправлений: 2)

любители указателей и множественного наследования.

множественного наследования

хмм, exec() не тот вызывается, который нужен?

Harald ★★★★★
()
Ответ на: комментарий от Harald

Это неверный ответ.

Но не расстраивайтесь, принять участие в нашей викторине можно неограниченное количество раз.

Так же мы ждем новых участников!

trex6 ★★★★★
() автор топика
Ответ на: комментарий от alex_custov

Не совсем понял, в чем вы видите проблему.
В обоих случаях qApp будет работать верно.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от alex_custov

P.S. Прошу прощения за мою безграмотность, но не могли бы вы подробнее описать, что такое atexit handler и с чем его едят. Ссылка на англоязычную документацию вполне подойдет.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от trex6

деструктор вызывается в разные моменты, во втором случае раньше, чем нужно?

множественное наследование тут какое-то отношение к сабжу имеет, или так, для красного словца? :)

Harald ★★★★★
()
Ответ на: комментарий от anonymous

Нет, с деструктором все в порядке.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от Harald

Смотрите на код, а не на комментарии. Код никогда не врет.

P.S. А может быть тут и нет вовсе никакой ошибки? Если вы так считаете - смело заявите об этом.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от trex6

лично я не припоминаю из документации ничего об UB в данном случае.

alex_custov ★★★★★
()
Ответ на: комментарий от slovazap

а, точно, там же ссылка. Сам такое дебажил год назад.

alex_custov ★★★★★
()

QApplication::QApplication(int & argc, char ** argv)

передаем ссылку на argc, который является временным объектом, тогда как:

The data referred to by argc and argv must stay valid for the entire lifetime of the QApplication object

dib2 ★★★★★
()

Неужели та же проблема, что и у гтк'шников: если вы подгрузили динамически glib, то не вздумайте её динамически выгружать, т.к. они уже нагадила в статике вашей проги и очищать её не собирается. В переводе на qt: если вы создали QApplication, не вздумайте его собственноручно удалять ибо он уже глубоко вгрызся в память вашей проги.

gag ★★★★★
()
Ответ на: комментарий от mannaz

Почему необъяснимых - Qt выдёргивает свои аргументы, и программа может не думать о том как их парсить. Хотя по-человечески она должна смотреть в env, и не лезть в аргументы.

slovazap ★★★★★
()
Ответ на: комментарий от trex6

Что ж разрабы языка не подумали о том, что могут быть такого рода разночтения?

В том же только так ссылку передать можно:

int lol (int *a) {*a = newval;} ... int i; lol(&i);

Да, какой-то этот цпп не такой

HNO-Arzt_
()

For any GUI application using Qt, there is precisely one QApplication object, no matter whether the application has 0, 1, 2 or more windows at any given time. For non-GUI Qt applications, use QCoreApplication instead, as it does not depend on the QtGui library.

The QApplication object is accessible through the instance() function that returns a pointer equivalent to the global qApp pointer.

Нельзя создавать QApplication оператором new?

Эх, не увидел ссылки.

nanoolinux ★★★★
()
Последнее исправление: nanoolinux (всего исправлений: 1)
Ответ на: комментарий от slovazap

Самое важное, что это грандиозная глупость!

trex6 ★★★★★
() автор топика
Ответ на: комментарий от slovazap

Нет, такой проблемы в Qt нет.

Это радует, но даже не верится, что они настолько педантично написали qtcore & qtgui, что я могу их dlopen()/dlclose() без утечек памяти.

Зато в gtk/C всё ясно, просто и бросается в глаза в отличие от плюсовой ссылки:

void gtk_init (int *argc,
               char ***argv);
Передаются указатели на argc и «массив» строк, значит, их хотят менять.

gag ★★★★★
()
Ответ на: комментарий от HNO-Arzt_

ЦПП нормальный, это разрабы Qt накосячили. Как минимум должны были этот момент записать по общей документации QApplication, а не прятать такую важную информацию в описание конструктора.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от slovazap

Логичнее было бы распарсить аргументы один раз в конструкторе QCoreApplication и хранить их в QStringList, чтобы последующие вызовы arguments() не декодировали их каждый раз из argc/argv. Все это больше похоже на какую-то нелепую оптимизацию, типа: «аргументы обычно нужны только при запуске приложения, поэтому давайте-ка сэкономим место и не будем хранить их копии; ну а раз храним указатель на argv, то заодно и argc будем хранить по ссылке, чтобы красивенько было». Бред.

mannaz
()
Ответ на: комментарий от gag

Конкретно тут дело не в ссылка/указатель. Хотят менять - пусть меняют. Но о том, что они эту ссылку будут _хранить_ надо громогласно предупредить, я считаю.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от solovey

Ссылка - как параметр конструктора. Если компнуть немного исходников - они эту ссылку сохраняют как ссылку и потом пользуют где ни попадя.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от trex6

Не знаток API иксов, но сомневаюсь, что эти аргументы нужны для чего-либо кроме инициализации. Ну а раз так, то и char* из QString получить не накладно.

mannaz
()
Ответ на: комментарий от mannaz

В моем пути при решении этого бага была вот такая остановка:

XSetWMProperties(
    dpy, id, 0, 0,
    qApp->d_func()->argv, qApp->d_func()->argc,
    &size_hints, &wm_hints, &class_hint
);

Это происходит перед первым показом диалога. Так что вы совсем не правы. Но Qt-шники могли бы хранить и просто int, вместо int&.

P.S. Пользуясь случаем посылаю лучи ненависти тому, кто сломал теги в "User line break"

P.P.S. И еще пони, которая сломала нормальные кавычки, тоже лучиков поноса.

trex6 ★★★★★
() автор топика
Последнее исправление: trex6 (всего исправлений: 3)
Ответ на: комментарий от trex6

А причём здесь хранить? Разве принято в приложении собственноручно менять argc/argv? Так что они и остаются валидными в течение всей работы.

Я понял, что технически UB возникает из-за ошибки со ссылкой. А причина, почему эта ссылка так важна как раз в том, что

Note: argc and argv might be changed as Qt removes command line arguments that it recognizes.

Т.е. qt возьмёт и поудаляет кое-какие элементы из argv, уменьшит соответственно argc, но основная программа об этом не узнает, т.к. argc изменили только локально и сразу же по выходу изменения эти потеряли.

gag ★★★★★
()
Ответ на: комментарий от gag

Проблема намного глубже.

Если бы QApplication просто поменял значения argc/argv во время работы конструктора - проблемы бы не возникало.

Но он сохраняет внутри QCoreApplicationPrivate _ссылку_ на переданный argc. ЗАЧЕМ?! Одному Тьяго известо.

Я считаю, что такие вещи должны быть жирным шрифтом прописаны в общем описании класса, а не скромно спрятаны в описании конструктора.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от trex6

Так что вы совсем не правы

«Первый показ диалога», «создание окна» и т.п. - это и есть инициализация. Да и никто не мешает хранить указатель на argv наравне с QString-копиями аргументов, если нужно. Тем более, что это скорее специфично для иксов - под виндой Qt в argc/argv вообще не лезет, насколько я знаю.

Но Qt-шники могли бы хранить и просто int, вместо int&

Это точно. Как я предположил выше, это как раз тот случай, когда одно бредовое решение потянуло за собой другое :)

Мне повезло, я довольно быстро разобрался в чем дело, т.к. были креши именно в вызове arguments(). Так далеко забираться не пришлось :)

mannaz
()
Ответ на: комментарий от mannaz

Вам действительно повезло.

В моем случае приложение крешилось только на arm и только на конкретном девайсе (т.е. на x86 все работало без проблем, на соседнем arm - без проблем, а на этом- Segmentation fault).
Причем не при запуске, а после некоторого времени работы. В приложении - несколько потоков, куча классов. Это было весело!

trex6 ★★★★★
() автор топика
Ответ на: комментарий от mannaz

Я valgrind под arm собрать не осилил. На x86 вроде бы на это не ругался. Да я и пропустить мок, т.к. типа в сторонней библиотеке и видимо проблем не доставит.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от invy

В QCoreApplicationPrivete::argc сохраняется ссылка на argc, переданный в конструктор. argc - был выделен на стеке и после завершения функции будет уничтожен, а на это место попадут какие-то другие данные. Как только приложение попробует получить доступ к argc - оно прочтет эти данные (в моем примере там скорее всего будет адрес приложения, т.к. следующим на стеке будет размещен QApplication* app). Это довольно большое число и, если основываясь на нем приложение начнет просматривать argv, вы скорее всегополучите segmentation fault, но тоже не обязательно. Парой комментов выше есть описание того, как это стреляло в ногу в моем случае.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от trex6

В QCoreApplicationPrivete::argc сохраняется ссылка на argc

да уже понял... почти «тему не читай сразу отвечай» :)

Конечно, я даже в страшном кошмаре не мог представить, что они ссылку сохраняют (зачем?!)

хотя нет, после return 12345; уже наверное мог бы.

invy ★★★★★
()
Последнее исправление: invy (всего исправлений: 2)
Ответ на: комментарий от trex6

Если бы QApplication просто поменял значения argc/argv во время работы конструктора - проблемы бы не возникало.

Как я выше упомянул, проблема, конечно же, возникает:

#include <iostream>
#include <string>

using namespace std;

int fake_argc_1 = 4;
char *fake_argv_1[4] = {"progname", "arg1", "arg2_qt", "arg3"};
int fake_argc_2 = 4;
char *fake_argv_2[4] = {"progname", "arg1", "arg2_qt", "arg3"};

void print_argv(int argc, char **argv)
{
        for (int i = 0; i < argc; ++i)
                cout << "    " << string(argv[i]) << endl;
}

void my_qapp_ok(int& argc, char **argv)
{
        // remove the _qt parameter, because it is only for qt
        argv[2] = argv[3];
        argv[3] = NULL;
        --argc;
}

void my_qapp_crash_after(int argc, char **argv)
{
        // remove the last parameter, because it is only for qt
        argv[2] = argv[3];
        argv[3] = NULL;
        --argc;
}

int main(int argc, char *argv[])
{
        cout << "reference:" << endl;
        print_argv(fake_argc_1, fake_argv_1);
        cout << "doing:" << endl;
        my_qapp_ok(fake_argc_1, fake_argv_1);
        cout << "result ok:" << endl;
        print_argv(fake_argc_1, fake_argv_1);

        cout << endl << "reference" << endl;
        print_argv(fake_argc_2, fake_argv_2);
        cout << "crash:" << endl;
        my_qapp_crash_after(fake_argc_2, fake_argv_2);
        print_argv(fake_argc_2, fake_argv_2);

        return 0;
}

Но он сохраняет внутри QCoreApplicationPrivate _ссылку_ на переданный argc.

А как можно ссылку _сохранить_? Указатель на ссылку можно, да. А ссылкой можно пользоваться только в области видимости объявления и передавать в другие функции.

gag ★★★★★
()
Ответ на: комментарий от gag

Действительно, можно и на этом еще пулю со смещенным центром тяжести получить. Блин! Qt-шники...

А ведь они даже патч не примут, который это исправляет - бинарная совместимость, мать ее.

Чтож, буду ждять Qt6, тогда им патчик и отправлю.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от trex6

Я valgrind под arm собрать не осилил

А поставить под qemu debian-armhf?

На x86 вроде бы на это не ругался. Да я и пропустить мок, т.к. типа в сторонней библиотеке и видимо проблем не доставит.

Наверняка, т.к. ругается:

Use of uninitialised value of size ...

gag ★★★★★
()
Ответ на: комментарий от gag

Проблема проявлялась только на одном из двух имеющихся arm устройств, так что про qemu не думалось, а потом уже все более-менее понятно стало.

trex6 ★★★★★
() автор топика
Ответ на: комментарий от trex6

Пробовал на struct: отличие же только в приватности по умолчанию. Да, оказывается, что если объявить ссылку приватной и обязательно добавить конструктор с её инициализацией, то можно.

gag ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.