using pointer to QOpenGLWidget cause app crash while exiting



  • I found weird bug with QOpenGLWidget when it starts running on second monitor (maybe it's general for 'not main' monitor). If I run my app on main monitor/screen it works normally but if it run on second monitor it's crashes while closing. I removed as many code as possible to make SSCCE code which reproduce this issue (it is below) and btw I found possible reason but it doesn't make sense to me.

    In main I have pointer to widget (it inherits upon QOpenGLWidget). App start normally, renders rgb triangle but when I try to close it, I receive:

    "Unhandled exception at 0x0000000067681C6A (Qt5Cored.dll) in QtTests.exe: 0xC0000005: Access violation reading location 0x0000000000000008.".

    If I declare it without 'new' (like usual object in scope) everything works perfect. It happens only when I have QOpenGLWidget inside (no matter how "deep"). I tested this with QWidget, QMenuBar, QMenu, QAction, QToolBar, QPushButton, QTabWidget and even QGLWidge and all is fine, it happens only for QOpenGLWidget. Do you have any idea what am I doing wrong? Why I cannot use pointer to widget if it contains QOpenGLWidget inside?

    file "main.cpp"
    #include "qttest.hpp"
    #include <QApplication>
    #include <QFontDatabase>
    
    int main(int argc, char* argv[]) {
        QApplication app(argc, argv);
    
        //this is not working
        OpenGLWidget* mainWindow; 
        mainWindow = new OpenGLWidget();
        mainWindow->show();
    
        //this is working
        //OpenGLWidget mainWindow;
        //mainWindow.show();
        return app.exec();
    }
    
    file "qttest.hpp"
    #ifndef QTTEST_HPP
    #define QTTEST_HPP
    #include <QPushButton>
    #include <QOpenGLWidget>
    #include <gl/GLU.h>
    #include <gl/GL.h>
    
    class OpenGLWidget : public QOpenGLWidget {
        Q_OBJECT
    public:
        explicit OpenGLWidget(QWidget* parent = 0);
    protected:
        void initializeGL();
        void paintGL();
        void resizeGL(int w, int h);
    private:
    signals :
        public slots :
    };
    
    #endif
    
    file "openglwidget.cpp"
    #include "qttest.hpp"
    
    OpenGLWidget::OpenGLWidget(QWidget* parent) : QOpenGLWidget(parent) {
        this->move(1800, 400); //set on main/other monitor
        this->resize(700, 300);
    }
    
    void OpenGLWidget::initializeGL() {
        glClearColor(0, 0, 0, 1);
        glEnable(GL_DEPTH_TEST);
    }
    
    void OpenGLWidget::paintGL() {
        glClearColor(0.1875, 0.1875, 0.1875, 1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glBegin(GL_TRIANGLES);
        glColor3f(1.0, 0.0, 0.0);
        glVertex3f(-0.5, -0.5, 0);
        glColor3f(0.0, 1.0, 0.0);
        glVertex3f(0.5, -0.5, 0);
        glColor3f(0.0, 0.0, 1.0);
        glVertex3f(0.0, 0.5, 0);
        glEnd();
    }
    
    void OpenGLWidget::resizeGL(int w, int h) {
        glViewport(0, 0, w, h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(45, (float)w / h, 0.01, 100.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
    }
    

    If it can helps there is stacktrace from Visual Studio:

    Qt5Cored.dll!QScopedPointer<QObjectData,QScopedPointerDeleter<QObjectData> >::data() Line 141   C++
    Qt5Cored.dll!qGetPtrHelper<QScopedPointer<QObjectData,QScopedPointerDeleter<QObjectData> > >(const QScopedPointer<QObjectData,QScopedPointerDeleter<QObjectData> > & p) Line 980    C++
    Qt5Cored.dll!QObject::d_func() Line 116 C++
    Qt5Cored.dll!QObject::thread() Line 1432    C++
    Qt5Guid.dll!QOffscreenSurface::create() Line 183    C++
    Qt5Guid.dll!QOffscreenSurface::setScreen(QScreen * newScreen) Line 328  C++
    Qt5Guid.dll!QOffscreenSurface::screenDestroyed(QObject * object) Line 342   C++
    Qt5Guid.dll!QOffscreenSurface::qt_static_metacall(QObject * _o, QMetaObject::Call _c, int _id, void * * _a) Line 83 C++
    Qt5Cored.dll!QMetaObject::activate(QObject * sender, int signalOffset, int local_signal_index, void * * argv) Line 3742 C++
    Qt5Cored.dll!QMetaObject::activate(QObject * sender, const QMetaObject * m, int local_signal_index, void * * argv) Line 3603    C++
    Qt5Cored.dll!QObject::destroyed(QObject * _t1) Line 214 C++
    Qt5Cored.dll!QObject::~QObject() Line 923   C++
    Qt5Guid.dll!QScreen::~QScreen() Line 139    C++
    [External Code] 
    Qt5Guid.dll!QPlatformIntegration::destroyScreen(QPlatformScreen * screen) Line 496  C++
    qwindowsd.dll!QWindowsIntegration::emitDestroyScreen(QPlatformScreen * s) Line 101  C++
    qwindowsd.dll!QWindowsScreenManager::clearScreens() Line 561    C++
    qwindowsd.dll!QWindowsContext::~QWindowsContext() Line 357  C++
    qwindowsd.dll!QWindowsIntegrationPrivate::~QWindowsIntegrationPrivate() Line 247    C++
    [External Code] 
    qwindowsd.dll!QScopedPointerDeleter<QWindowsIntegrationPrivate>::cleanup(QWindowsIntegrationPrivate * pointer) Line 60  C++
    qwindowsd.dll!QScopedPointer<QWindowsIntegrationPrivate,QScopedPointerDeleter<QWindowsIntegrationPrivate> >::~QScopedPointer<QWindowsIntegrationPrivate,QScopedPointerDeleter<QWindowsIntegrationPrivate> >() Line 108  C++
    qwindowsd.dll!QWindowsIntegration::~QWindowsIntegration() Line 264  C++
    qwindowsd.dll!QWindowsGdiIntegration::~QWindowsGdiIntegration() Line 62 C++
    [External Code] 
    Qt5Guid.dll!QGuiApplicationPrivate::~QGuiApplicationPrivate() Line 1502 C++
    Qt5Widgetsd.dll!QApplicationPrivate::~QApplicationPrivate() Line 199    C++
    [External Code] 
    Qt5Cored.dll!QScopedPointerDeleter<QObjectData>::cleanup(QObjectData * pointer) Line 60 C++
    Qt5Cored.dll!QScopedPointer<QObjectData,QScopedPointerDeleter<QObjectData> >::~QScopedPointer<QObjectData,QScopedPointerDeleter<QObjectData> >() Line 108   C++
    Qt5Cored.dll!QObject::~QObject() Line 1049  C++
    Qt5Cored.dll!QCoreApplication::~QCoreApplication() Line 854 C++
    Qt5Guid.dll!QGuiApplication::~QGuiApplication() Line 629    C++
    Qt5Widgetsd.dll!QApplication::~QApplication() Line 895  C++
    QtTests.exe!main(int argc, char * * argv) Line 18   C++
    QtTests.exe!WinMain(HINSTANCE__ * __formal, HINSTANCE__ * __formal, char * __formal, int __formal) Line 123 C++
    [External Code] 
    

    I'm working in Visual Studio 2013 Community with Qt add-in 1.2.5 and Qt 5.7 (msvc2013_64).


  • Moderators

    Hi, welcome to the forum.

    The problem is that you have a leak. You never delete the instance of QOpenGLWidget. No QObject derived class is allowed to live past the lifetime of QApplication instance.

    In short:

    int main(int argc, char* argv[]) {
        QApplication app(argc, argv);
    
        //this is not working
        OpenGLWidget* mainWindow; 
        mainWindow = new OpenGLWidget();
        mainWindow->show();
    
        int result = app.exec();
        delete mainWindow; // <- THIS LINE IS IMPORTANT
        return result;
    }
    

    That's the same rule for all QObject based class. The fact that it worked for QWidget, QPushButton etc. is pure luck and undocumented behavior. Don't do that.

    Also, in this particular case, there's no reason to create the instance on the heap. Stack allocated instance will be easier, faster and less prone to errors (like this one here).



  • @Chris-Kawa Thank you for your help, it works. And if we talk about this case, it is only an example. In app which produced this problem I decided to use heap instead of stack, cause this "problematic" window is created after closing splash screen (started for login), so as far as I know (may be wrong) I cannot use stack for uninitialized objects.
    And one more thing, I'm just curious. Why this problem appears only when window was created on second screen. Is it Qt working different when uses only one screen or something? It is so unintuitive when you found a bug which happens in some cases and there is no dependency between problem and why it happend. I mean, this particular problem, appears only in multiscreen environment, but numbers of screens doesn't matter, cause the problem was in GC. Honestly, because it happend for me while closing I wasn't even thinking it might be GC problem. And because app was closing, so even when I didn't close any widget properly they will be destroyed by an OS when app releases the memory. Of course correct memory management is important, but in this case I can don't care about that.


  • Moderators

    @NRUB said:

    And because app was closing, so even when I didn't close any widget properly they will be destroyed by an OS when app releases the memory. Of course correct memory management is important, but in this case I can don't care about that.

    This issue is not about memory management. Whether to clean up all allocations or leave it to OS is a design decision and a holy war that I don't want to get into.

    This issue is about correctness. The documented Qt contract is that no QObject derived class instance shall outlive QApplication instance, otherwise no behavior is specified or guaranteed. You just experienced that - an application object went out of scope and was destroyed while a QObject derived QOpenGLWidget was still alive. End of main() does not mean end of application execution and there's quite some amount of code still getting executed before it gets to the point that OS cleans up leftovers.

    The fact that it causes problems only in some scenarios (multiscreen) is irrelevant. There's some implementation detail that manifests itself this way. Whether it behaves the way you want or not doesn't change the fact that it is incorrect. Sometimes that incorrectness has no harmful side effects, sometimes it does.

    It's your choice whether to cleanup everything or not but if you don't then at least make it explicit, like by calling std::abort. Otherwise you're just ignoring the problem, which doesn't make it go away, code still gets executed - as you just observed.


  • Qt Champions 2016

    @Chris-Kawa

    It's your choice whether to cleanup everything or not but if you don't then at least make it explicit, like by calling std::abort.

    Chris, that advice is just like giving someone a shotgun to blow his foot off.

    @NRUB

    Just declare your objects' lifetimes clearly and you won't have any problems. QObjects aren't (ordinarily) supposed to be created before QCoreApplication::QCoreApplication() has run, or live after QCoreApplication::~QCoreApplication(), although I'll grant you the documentation is pretty vague about it. So given that you don't care to delete the widget, how is the OS supposed to know to run its destructor before the main()'s stack is unwound? That's one of the reasons to have the nice parent-child relationship for QObjects, so the parent makes sure the children are destroyed before it gets destroyed ... or, just do what everyone else does and create the "main"/"root" widget in the stack after you create the application object, I believe this is also the way used in all (or perhaps almost all) the examples in the documentation.

    Kind regards.


  • Moderators

    @kshegunov said:

    Chris, that advice is just like giving someone a shotgun to blow his foot off.

    To me anybody suggesting to leave cleanup to OS is someone carrying a bag of explosives on their back, so I'll assume they can handle firearms :P
    Yeah, you're right. I should probably not mention it or add a big fat warning about what it entails.



  • I know how parenting works in Qt and the idea is clear to me. I was just expecting that QObject will be destroyed while QApplication is finished but it didn't happend, so is it possible to use QApplication instance as a parent for QObjects? Something like: when my QWidget has no parent then it is alive as long as app is working and it is managed and deleted by app.
    And one more thing, when I said that OS is removing leftovers when app is finishing I was talking about RAM management system in OS which as we know works like a bridge between real addressing space and page addressing space which is translated by OS. Where apps in OS are using own addresses and these addreses are translated by OS when data are transfered from/to memory. So when app is destroyed all their pages returns to OS and can be used again by other app so in practice memory is not leaking when apps are destroyed.


  • Qt Champions 2016

    @NRUB

    I was just expecting that QObject will be destroyed while QApplication is finished

    Why? Qt doesn't track all objects, it guarantees only that the parent will delete the child, nothing more. So if you don't give a QObject a parent it'll just float out there and no one will delete it automatically, thus it will leak.

    is it possible to use QApplication instance as a parent for QObjects

    It is, because QCoreApplication (and consequently all its derived classes) is already a QObject. That's one of the reasons it's also sometimes referred to as the "root QObject".

    when my QWidget has no parent then it is alive as long as app is working and it is managed and deleted by app.

    You can't give a QWidget instance a QObject parent, but you can connect the QCoreApplication::aboutToQuit() signal to the QObject::deleteLater() slot and this'd solve your leakage problems.
    For example:

    int main(int argc, char ** argv)
    {
        QApplication app(argc, argv);
    
        QWidget * window = new QWidget();
        QObject::connect(&app, &QCoreApplication::aboutToQuit, window, &QObject::deleteLater);
        
        window->show();
        return QApplication::exec();
    }
    

    OS is removing leftovers when app is finishing

    Which means that claimed memory will be released by the OS, but here the problem is that you actually need to have the heap allocated objects' destructors invoked, which the OS will not do.

    which as we know works like a bridge between real addressing space and page addressing space which is translated by OS

    Yes, we know that, but there are two issues with this. Firstly, there may not be any difference between physical and logical addressing and this is common when the OS is ran without paging; like I run my Linux without a swap partition. And secondly, paging has next to nothing in common with the problem here.

    in practice memory is not leaking when apps are destroyed

    Even if this were true, which it is for most OS-es, not taking care to clean up your resources leads to all kinds of funky stuff. For the most simple and obvious of examples - a driver holding a global resource is not deinitialized, and the global resource is left locked for the next program wanting to use it. Then what, should we restart the whole system?

    Just leaving the OS to clean up, especially in such obvious circumstances, is in my opinion a lazy approach to programming that does more harm than good in the long run. Not only don't a bit of discipline and a handful of good practices hurt, on the contrary, they will spare you the trouble of digging through deep stack traces unnecessarily ...

    Kind regards.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.