QtObject a zachowanie wskaźników

0

Cześć, uczę się frameworka Qt do języka C++ i mam mały problem z odpowiedzią na pytanie zawarte w książce, z której się uczę a mianowicie "C++ i Qt Wprowadzenie do wzorców projektowych". Poniższy listing pochodzi pochodzi też z tej książki i nie jestem jego autorem.

Oto fragment listingu:

// wyhoduj gromadkę
void growBunch() {
    qDebug() << "Stwórzmy najpierw gromadkę obiektów." << endl;
    QObject bunch;
    bunch.setObjectName("Obiekt na stosie");
    /* inne obiekty są tworzone na stercie */
    Person* mike = new Person("Mike", &bunch);
    Person* carol = new Person("Carol", &bunch);
    new Person("Greg", mike); 
    new Person("Peter", mike);
    new Person("Robert", mike);
    new Person("Marcia", carol);
    new Person("Jan", carol);
    new Person("Cindy", carol);
    new Person("Alice");                                 /* Alice nie ma rodzica — wyciek pamięci? <-- to pytanie */
    qDebug() << "\nWyświetl listę za pomocą QObject::dumpObjectTree()"
             << endl;
    bunch.dumpObjectTree();                              /* Wynik dumbObjectTree() pojawi się na ekranie, tylko jeśli biblioteka Qt została skompilowana z włączona opcją debug. */
    cout << "\nMożemy zwrócić wartości z growBunch() -"
         << " Zniszczenie wszystkich obiektów na stosie." << endl;
}

O ile rozumiem to, że QObject opiekuje się swoimi dziećmi oraz mike i carol są dziećmi rodzica bunch, który jest zmienną umieszczoną na stosie i zostanie wywołany jego destruktor zaraz po wyjściu z funkcji i usunie on wszystkie swoje dzieci i dzieci dzieci ze sterty, to co do Person("Alice") wydaje mi się że wyciek pamięci będzie, no bo skoro nie ma ona żadnego rodzica, a funkcja nie korzysta z delete dla Alicji, to nie ma kto jej usunąć. Autor niestety na to pytanie nie odpowiada, a chciałbym się upewnić, że dobrze rozumiem to, co się tutaj dzieje. A góry przepraszam, jeśli pytanie jest głupie, ale budzi to moje wątpliwości. Z góry dziękuję za pomoc :)

1

Tak, powinieneś mieć wyciek pamięci w tym miejscu, dziwne, że autor tego nie opisał.

Przy okazji, takie rzeczy możesz sprawdzić np. valgrindem ;)

Np. tak:

[pts/0:krzaq@krzaq1:~/temp/qt]% cat -n m.cpp
     1  #include <iostream>
     2
     3  #include <QDebug>
     4  #include <QObject>
     5
     6  using namespace std;
     7
     8  struct Person : public QObject{
     9          Person(QString n, QObject* parent = nullptr) : QObject(parent) {
    10                  setProperty("name", n);
    11          }
    12  };
    13
    14  void growBunch() {
    15          qDebug() << "Stwórzmy najpierw gromadkę obiektów." << endl;
    16          QObject bunch;
    17          bunch.setObjectName("Obiekt na stosie");
    18          /* inne obiekty są tworzone na stercie */
    19          Person* mike = new Person("Mike", &bunch);
    20          Person* carol = new Person("Carol", &bunch);
    21          new Person("Greg", mike);
    22          new Person("Peter", mike);
    23          new Person("Robert", mike);
    24          new Person("Marcia", carol);
    25          new Person("Jan", carol);
    26          new Person("Cindy", carol);
    27          new Person("Alice");                                                             /* Alice nie ma rodzica — wyciek pamięci? <-- to pytanie */
    28          qDebug() << "\nWyświetl listę za pomocą QObject::dumpObjectTree()"
    29                           << endl;
    30          bunch.dumpObjectTree();                                                   /* Wynik dumbObjectTree() pojawi się na ekranie, tylko jeśli biblioteka Qt została skompilowana z włączona opcją debug. */
    31          cout << "\nMożemy zwrócić wartości z growBunch() -"
    32                   << " Zniszczenie wszystkich obiektów na stosie." << endl;
    33  }
    34
    35  int main()
    36  {
    37          growBunch();
    38  }
    39
[pts/0:krzaq@krzaq1:~/temp/qt]% qmake qt.pro -r -spec linux-g++ CONFIG+=debug
[pts/0:krzaq@krzaq1:~/temp/qt]% make
g++ -c -pipe -std=c++11 -g -Wall -W -D_REENTRANT -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4 -I. -I. -o m.o m.cpp
g++  -o qt m.o    -L/usr/lib/qt4 -lQtCore -L/usr/lib/qt4 -lgthread-2.0 -lglib-2.0 -lpthread
{ test -n "" && DESTDIR="" || DESTDIR=.; } && test $(gdb --version | sed -e 's,[^0-9]\+\([0-9]\)\.\([0-9]\).*,\1\2,;q') -gt 72 && gdb --nx --batch --quiet -ex 'set confirm off' -ex "save gdb-index $DESTDIR" -ex quit 'qt' && test -f qt.gdb-index && objcopy --add-section '.gdb_index=qt.gdb-index' --set-section-flags '.gdb_index=readonly' 'qt' 'qt' && rm -f qt.gdb-index || true
[pts/0:krzaq@krzaq1:~/temp/qt]% valgrind --leak-check=full ./qt
==6856== Memcheck, a memory error detector
==6856== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==6856== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==6856== Command: ./qt
==6856==
Stwrzmy najpierw gromadk obiektw.


Wywietl list za pomoc QObject::dumpObjectTree()


Możemy zwrócić wartości z growBunch() - Zniszczenie wszystkich obiektów na stosie.
==6856==
==6856== HEAP SUMMARY:
==6856==     in use at exit: 550 bytes in 14 blocks
==6856==   total heap usage: 212 allocs, 198 frees, 72,551 bytes allocated
==6856==
==6856== 226 (8 direct, 218 indirect) bytes in 1 blocks are definitely lost in loss record 14 of 14
==6856==    at 0x402B7A4: operator new(unsigned int) (vg_replace_malloc.c:292)
==6856==    by 0x804978A: growBunch() (m.cpp:27)
==6856==    by 0x80499AA: main (m.cpp:37)
==6856==
==6856== LEAK SUMMARY:
==6856==    definitely lost: 8 bytes in 1 blocks
==6856==    indirectly lost: 218 bytes in 7 blocks
==6856==      possibly lost: 0 bytes in 0 blocks
==6856==    still reachable: 324 bytes in 6 blocks
==6856==         suppressed: 0 bytes in 0 blocks
==6856== Reachable blocks (those to which a pointer was found) are not shown.
==6856== To see them, rerun with: --leak-check=full --show-reachable=yes
==6856==
==6856== For counts of detected and suppressed errors, rerun with: -v
==6856== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

Jak widać wyciek jest wyłącznie tam gdzie tego oczekiwałeś.

0

Bardzo dziękuję. Szczególnie za pokazanie programu valgrind, bo wcześniej o nim nie miałem pojęcia :)

1

Bez wątpienie będzie to wyciek pamięci, ale dobrze by było gdybyś skorzystał z jakiegoś narzędzia do sprawdzania pamięci by to potwierdzić.
Z Qt Creatorem jest chyba standardowo dostarczone takie narzędzie. Pod Linux-em jest to Valgrind (nie wiec co jest pod Windows).
Uruchamia się to przez menu "Analiza/Analizator pamięci Valgrind" (niestety narzędzie to jest strasznie powolne, program działa około 30 razy wolniej).

1

Od którejś wersji GCC (4.8) jest wsparcie dla wykrywania mem-leaków (Address Sanitizer). Rozwiązanie to jest lepsze od valgrind-a, bo program zwalnia tylko około 2 razy i powinno tez działać pod Windows (poza tym Valgrind ma pewne ograniczenia związane ze stosem, więc Address Sanitizer powinien być pewniejszy):
http://blog.qt.digia.com/blog/2013/04/17/using-gccs-4-8-0-address-sanitizer-with-qt/
Sam jeszcze nie miałem okazji wypróbować.

3

Wadą address sanitizera jest konieczność kompilacji z flagą włączającą go - dla większego projektu to chwilkę potrafi potrwać, a odpalenie valgrind współpracuje z normalnymi debugowymi binarkami. Na pewno jednak jest to narzędzie godne uwagi.

0

Żeby nie tworzyć nowego wątku. Zainstalowałem sobie plugin valgrind do QtCreatora. Kiedy sprawdzam wycieki pamięci bez zaznaczonej falgi external errors w moim bieżącym projekcie wszystko jest czyste, ale załączając tę flagę mam aż kilkaset znalezionych problemów (koło 400), a wiele z nich (początkowe informacje o błędach) wskazują na utworzenie w funkcji main obiektu QApplication. Większość z błędów to błędy typu possibly lost, jest kilka definitely lost oraz invalid read of size 4. Świadczy to o złym użyciu bibliotek, czy są to błędy kompletnie ode mnie niezależne?

0

Sprawdź na nowym czystym projekcie i oczywiście przy poprawnym zamknięciu aplikacji.

0

Nieco mniej tych błędów w czystym projekcie, ale wciąż blisko 400, więc podejrzewam, że nie są to błędy zależne ode mnie :)

0

W takim wypadku nie są to rzeczy od Ciebie zależne (ale fajnie by to było sprawdzić z debugowym buildem Qt).

1

Jeśli jest to wtyczka Valgrinda przeznaczona do Qt Creatora, to logi powinieneś mieć czyste.
Przynajmniej jak ja używałem valgrinda, który był dostarczony z Qt Creator'em, to po wyczyszczeniu mem leaków logi Valgrinda miałem zupełnie czyste.
Zapewne taka wersja posiada podpiętą listę fałszywych alarmów, które są potem ignorowane.

Anyway, jak miałem jeden leak, to w logach pojawiło się od groma informacji, bo Valgrind nie tylko raportował właściwy leak (definitely lost), ale też wszystkie obiekty dzieci (possibly lost).

Pokaż czysty projekt.

1 użytkowników online, w tym zalogowanych: 0, gości: 1