QCustomPlot Discussion and Comments

Crash in QCustomPlot destructorReturn to overview


I am trying to delete a dynamically allocated QCustomPlot object. The program crashes in
delete mPlotLayout; Finally it crashes at

patran.exe!QCPLayerable::~QCPLayerable() Line 955 C++

Any help to fix would be greatly appreciated.

Hi Sisira,

does this happen in a new blank project? Have you run through memory profilers and such to make sure there are no other memory problems/double frees etc. in the code?
Finally I'd also be interested in the Qt version, Compiler, QCP version and OS you're using.

Hi DerManu:
I am using in an old project. The example applications run with no issues. In fact, I modeled my code based on interaction example. Currently, I just transplanted it on to my code to test it, before actual QCustomPlot integration.

I will run though a memory profile as to find what is going on. Will keep you informed.
I am using the latest version both. Qt 5.4.0 and QCP 1.3.0 I also have Qtitan 3.5.0. Visual Studio 2012 and compiled in 64-bit.

Hi DerManu, First, tahnk you very much for the widget, it is very useful and powerful.

I am having exactly the same problem as described here, but only in some computers. My application uses:
Windows 7 32 bits
VS2012
Qt 5.2.1
QCustomPlot 1.3.0
The aplication creates a derived class from QCustomPlot and add the derived class to a QHBoxlayout.
Later there are some other QWidgets and layout on the same QDialog that pertain to the application.

Everything goes well but after close the application, windows report a problem closing the application.
I would like to make note that it does not happen in all PC, I have got only some computers that show the problem, but not in others. I could send you a snap shot of the call stack on VS2012 at the time of the crash.

Hi James,

It would indeed be very helpful if you could send me a screenshot of the call stack via email. If you can/are permitted to, and the project isn't too complex, the code that generates that behaviour would also help. Until now, I couldn't reproduce the behaviour and memory profilers also don't see issues.

Can you replicate this behaviour with a fresh project somehow, or does it only appear in the context of your application?

I'm getting a similar crash as well.

Platform: Windows 8.1, Visual Studio 2015, 32bit target, Qt 5.4.1

Same situation when the QCustomPlot destructor gets called I get a "..Invalid address specified to RtlValidateHeap(..", which is the debugger detecting a heap problem.

To troubleshoot the issue this is all I do:

customPlot = new QCustomPlot();
delete customPlot;

I've been looking at the call stack and stepping through the code.
Here is the relative portion of the call stack on crash:

KernelBase.dll!_HeapValidate@12()	Unknown
ucrtbased.dll!__CrtIsValidHeapPointer()	Unknown
ucrtbased.dll!__tolower()	Unknown
ucrtbased.dll!__free_dbg()	Unknown
QcpTest.dll!operator delete(void * block) Line 17	C++
QcpTest.dll!QT::QtSharedPointer::ExternalRefCountData::operator delete(void * ptr) Line 161	C++
QcpTest.dll!QT::QtSharedPointer::ExternalRefCountData::`scalar deleting destructor'(unsigned int)	C++
QcpTest.dll!QT::QWeakPointer<QT::QObject>::~QWeakPointer<QT::QObject>() Line 588	C++
QcpTest.dll!QT::QPointer<QCPAxis>::~QPointer<QCPAxis>() Line 64	C++
QcpTest.dll!QCPAxisRect::~QCPAxisRect() Line 11782	C++
QcpTest.dll!QCPAxisRect::`scalar deleting destructor'(unsigned int)	C++
QcpTest.dll!QCPLayout::removeAt(int index) Line 2195	C++
QcpTest.dll!QCPLayout::clear() Line 2234	C++
QcpTest.dll!QCPLayoutGrid::~QCPLayoutGrid() Line 2495	C++
QcpTest.dll!QCPLayoutGrid::`scalar deleting destructor'(unsigned int)	C++
QcpTest.dll!QCustomPlot::~QCustomPlot() Line 9097	C++
QcpTest.dll!QCustomPlot::`scalar deleting destructor'(unsigned int)	C++
QcpTest.dll!term() Line 27	C++

You can see it happens when the nearest QCP function ~QCPAxisRect() gets called (10 lines down).
Then what you can't see there there is actually what's happening in code that unfortunately Visual Studio doesn't show directly.
Code that gets called just before ~QCPAxisRect() returns:

--- I identified this as the class: QPointer<QCPAxis> mRangeDragHorzAxis, mRangeDragVertAxis, mRangeZoomHorzAxis, mRangeZoomVertAxis getting destructed:

076D6F71  mov         ecx,dword ptr [this]  
076D6F74  add         ecx,0CCh  
076D6F7A  call        QT::QPointer<QCPAxis>::~QPointer<QCPAxis> (07691366h) 

076D6F7F  mov         ecx,dword ptr [this]  
076D6F82  add         ecx,0C4h  
076D6F88  call        QT::QPointer<QCPAxis>::~QPointer<QCPAxis> (07691366h)  

076D6F8D  mov         ecx,dword ptr [this]  
076D6F90  add         ecx,0BCh  
076D6F96  call        QT::QPointer<QCPAxis>::~QPointer<QCPAxis> (07691366h)  <-- Crashes in here

When QPointer<QCPAxis>::~QPointer<QCPAxis>() is called it references it's internal "QWeakPointer<QT::QObject>" which in turn has a "QtSharedPointer".

The exact delete that is causing the problem I traced to the destructor in "struct ExternalRefCountData" of the QtSharedPointer namespace.
QtCore\qsharedpointer_impl.h (again QT 5.4.1)

namespace QtSharedPointer {
....
 struct ExternalRefCountData
{
  ...
        inline void operator delete(void *ptr) { ::operator delete(ptr); }   <---- THIS ONE  @ line #161
        inline void operator delete(void *, void *) { }
    };

...


If I temporarily comment out that /*::operator delete(ptr);*/ then no memory corruption.
With the simple example of construct and destruct this delete get's called twice (and they are both unique addresses).
So far it looks like (if the order of variables are correct) for what ever reason these two cause the corruption:
QPointer<QCPAxis> mRangeZoomHorzAxis
QPointer<QCPAxis> mRangeZoomVertAxis

Now from what I understand QPointer order can be tricky.
Furthermore in "qPointer.cpp" I see the comment:

Note that Qt 5 introduces a slight change in behavior when using QPointer.

    \list

    \li When using QPointer on a QWidget (or a subclass of QWidget), previously
    the QPointer would be cleared by the QWidget destructor. Now, the QPointer
    is cleared by the QObject destructor (since this is when QWeakPointers are
    cleared). Any QPointers tracking a widget will \b NOT be cleared before the
    QWidget destructor destroys the children for the widget being tracked.

    \endlist

Could it be there is a problem case in the QPointer destruct order where something is getting referenced that is already gone (because of unexpected sequence/order)?

Actually just noticed this:

void QCPAxisRect::setRangeZoomAxes(QCPAxis *horizontal, QCPAxis *vertical)
{
  mRangeZoomHorzAxis = horizontal;
  mRangeZoomVertAxis = vertical;
}

Stepping through in the debugger I see that horizontal and vertical objects are constructed before calling this function.
As you can see on return from this function mRangeZoomHorzAxis and mRangeZoomVertAxis hold now hold a reference to those objects too.

Debug variable dump:

horizontal = 0x02bb29f0
vertical = 0x02bb29c0

-		mRangeZoomHorzAxis	{wp={d=0x02bb29f0 {weakref={_q_value=0x00000004 } strongref={_q_value=0xffffffff } destroyer=0x00000000 } ...} }	QT::QPointer<QCPAxis>
-		wp	{d=0x02bb29f0 {weakref={_q_value=0x00000004 } strongref={_q_value=0xffffffff } destroyer=0x00000000 } ...}	QT::QWeakPointer<QT::QObject>
+		d	0x02bb29f0 {weakref={_q_value=0x00000004 } strongref={_q_value=0xffffffff } destroyer=0x00000000 }	QT::QtSharedPointer::ExternalRefCountData *
+		value	0x02b56590 {mAxisType=atBottom (0x00000008) mAxisRect=0x02b5d558 {mBackgroundBrush={d={d=0x00a02d58 {...} } } ...} ...}	QT::QObject * {QCPAxis}

-		mRangeZoomVertAxis	{wp={d=0x02bb29c0 {weakref={_q_value=0x00000004 } strongref={_q_value=0xffffffff } destroyer=0x00000000 } ...} }	QT::QPointer<QCPAxis>
-		wp	{d=0x02bb29c0 {weakref={_q_value=0x00000004 } strongref={_q_value=0xffffffff } destroyer=0x00000000 } ...}	QT::QWeakPointer<QT::QObject>
+		d	0x02bb29c0 {weakref={_q_value=0x00000004 } strongref={_q_value=0xffffffff } destroyer=0x00000000 }	QT::QtSharedPointer::ExternalRefCountData *
+		value	0x06bf6998 {mAxisType=atLeft (0x00000001) mAxisRect=0x02b5d558 {mBackgroundBrush={d={d=0x00a02d58 {ref=...} } } ...} ...}	QT::QObject * {QCPAxis}

A little hard to read maybe but you can see the perspective "value" fields hold references to these pointers.

Then sure enough the pointers 0x02bb29f0 and 0x02bb29c0 are what are causing the ::operator delete(ptr); memory corruption crash!

Could it be that the original QCPAxis objects are being destructed twice, once in another class destructor then a 2nd time via the QPointer<QCPAxis> destructor?

I couldn't wait for you, back at it again.

The xAxis, yAxis created axis objects gets deleted in this loop first:

QCPAxisRect::~QCPAxisRect()
{
  delete mInsetLayout;
  mInsetLayout = 0;
  
  QList<QCPAxis*> axesList = axes();
  for (int i=0; i<axesList.size(); ++i)
    removeAxis(axesList.at(i)); <-- The "delete axis;" in here
}

Then again and a third time when this destructor returns, "QPointer<QCPAxis> mRangeDragHorzAxis, mRangeDragVertAxis, mRangeZoomHorzAxis, mRangeZoomVertAxis;" own destructors are called..


An oddity I see where mRangeDragHorzAxis = mRangeZoomHorzAxis and mRangeDragVertAxis = mRangeZoomVertAxis.
Look here in the QCPAxisRect() constuctor:

    ...
    QCPAxis *xAxis = addAxis(QCPAxis::atBottom);
    QCPAxis *yAxis = addAxis(QCPAxis::atLeft);
    QCPAxis *xAxis2 = addAxis(QCPAxis::atTop);
    QCPAxis *yAxis2 = addAxis(QCPAxis::atRight);
    setRangeDragAxes(xAxis, yAxis); <-- mRangeDragHorzAxis are set here mRangeDragVertAxis
    setRangeZoomAxes(xAxis, yAxis); <-- Should this be xAxis2,yAxis2 ?  mRangeZoomHorzAxis, mRangeZoomVertAxis are set with the same pointers.
    ..

See so mRangeDragHorzAxis is the same pointer as mRangeZoomHorzAxis. Kind of redundant.

Hi Kevin,

Thanks for the reports here, this is very helpful. I haven't yet fully understood what's going on. Since QCPAxis are QObjects and not QWidgets, the QPointer change in Qt5 can't affect the issue at hand. So if a QCPAxis is deleted anywhere, all QPointers that point to it should be correctly cleared, and thus not deleted a second time through a direct call to the QPointer.
You said earlier: "Could it be that the original QCPAxis objects are being destructed twice, once in another class destructor then a 2nd time via the QPointer<QCPAxis> destructor." But that's a misconception: QPointer doesn't delete the object it guards when it itself is destroyed/goes out of scope.

Just to be sure, you are using QCP 1.3.2, right? Because that last patch included a fix of the QObject destruction order that had to do with QCPAxisRect and QCPAxis.

Setting rangeDrag/ZoomAxes to the same axes is intentional. After all, by default you want to drag and zoom the same axes: bottom and left (xAxis and yAxis). Holding two QPointers to the same object in QCPAxisRect shouldn't be a problem, because QPointer tracks QObjects and the second redundant one gets cleared once the other one is deleted. What I'm wondering: If this is a simple systematic double-delete, then my memory checker (valgrind memcheck) should have also caught it. I have the feeling this is either Qt-implementation specific to the platform or MSVC, and thus more subtle.

As always, if you have any more insights, please share. I'll think through the code and your findings as well but once again, I'm dependent on such reports since I can't reproduce it so far. And the sightings of this type of crash are extremely rare.

Glad to help. Love your project.

I found one more problem case:
In the QCPLayerable class, the QPointer<QCPLayerable> mParentLayerable; gets deleted more than once.
If I just make it a straight pointer then no problem, although there could be a memory leak someplace.

Yea, using your latest build QCP 1.3.2.

Could be certainly something specific to Visual Studio 2015.
Maybe something different in the GCC destructor order et al.
Another idea maybe in GCC when the pointer = operator assignment the object gets copied?

I'm sure of the problem flow. I've walked through it many times.
See in the for (int i=0; i<axesList.size(); ++i) loop all the mAxes instances get explicitly deleted.
With the simple test (new QCustomPlot(), then delete right after) the axesList.size() is 4.
Two of them are those rangeDrag/ZoomAxes instances.
So that is the first time they are deleted.
Now as ~QCPAxisRect() returns internally all those (four in total) QPointer<QCPAxis> get now deleted again.
Each of those ZoomAxes pointers get deleted three times.
Once in the loop, again in one QPointer<QCPAxis>, then finally a third time with the second QPointer<QCPAxis> destructor.

I see what your saying now. If they are ref counted, they should only get deleted once right?

You are correct that in the ~QCPAxisRect destructor, the QCPAxis instances are deleted.

However now the part where I don't agree: "Now as ~QCPAxisRect() returns internally all those (four in total) QPointer<QCPAxis> get now deleted again."
This is not the case. Like I said, QPointer only guards (monitors), it does not own the object it points to. As such, when the QCPAxisRect destructor returns and the pointer goes out of scope, it does not delete the object it points to. Even if it did own the object, at that point it has already been notified by the first delete that the object is now deleted and the pointer becomes null. If you really see these delete calls happen multiple times, that's a very serious Qt bug of the QPointer implementation on the windows platform. I almost can't imagine that wouldn't have been spotted and resolved far before the respective Qt release...

You can test the QPointer monitoring behaviour yourself, by tweaking the ~QCPAxisRect destructor temporarily with qDebug output:

QCPAxisRect::~QCPAxisRect()
{
  delete mInsetLayout;
  mInsetLayout = 0;
  
  qDebug() << "before:";
  qDebug() << mRangeDragHorzAxis.isNull() << mRangeDragVertAxis.isNull();
  qDebug() << mRangeZoomHorzAxis.isNull() << mRangeZoomVertAxis.isNull();
  
  QList<QCPAxis*> axesList = axes();
  for (int i=0; i<axesList.size(); ++i)
    removeAxis(axesList.at(i));
  
  qDebug() << "after:";
  qDebug() << mRangeDragHorzAxis.isNull() << mRangeDragVertAxis.isNull();
  qDebug() << mRangeZoomHorzAxis.isNull() << mRangeZoomVertAxis.isNull();
}

You should see the following:

before:
false false
false false
after:
true true
true true

Which tells us that after that first delete, the QPointers are informed that the object is gone and nothing will be able to call a double-delete through them (or even in their own destructor).

Yea strange I see what your saying.

The thing is though what these are created newAxis = new QCPAxis(this, type);, then appended to QHash<QCPAxis::AxisType, QList<QCPAxis*>> mAxes;.
So that now you have one thing that is containing the pointer.
Then you add it to a QPointer<QCPAxis> which now also contains them as a separate instance.

You delete the QCPAxis* from the list container.
What happens here is that when QPointer<QCPAxis> loose scope, they also delete the same QCPAxis* (thus the problem). The QPointer<QCPAxis> are not NULL by the time their desstructors are called.
Evidently this behavior is different in your builds.

I think though this is not a good paradigm. Why use these QPointer then if you delete the original pointer anyhow? And at that point they no longer hold a valid object. You could just use straight pointers for them.
I understand the idea is probably to let the QPointer/smart-pointer do the work for you rather than have to track and maintain each new/delete pair.

Yea different I'm getting:

before:
false false
false false
after:
false false
false false

P.S. I see above the OP and the other are also using Visual Studio.

You still have a misconception of what QPointer is doing. Please read the description of the QPointer documentation. It tracks QObjects, so whenever a raw pointer to a QObject is deleted, all other QPointers that hold that object, get notified and become null.
For example:

QSomeQObject *obj = new QSomeQObject();
QPointer<QSomeQObject> p = obj;
delete obj; // in this moment, p will become 0, and not a dangling pointer.

You then say: "I think though this is not a good paradigm. Why use these QPointer then if you delete the original pointer anyhow?"
This is exactly the paradigm that QPointer is made for. I think you are confusing it with other smart pointers like QSharedPointer or std::auto_ptr. QPointer is fundamentally different. QPointer can monitor QObjects even if you continue using raw pointers for deletion -- this would indeed not work with the other smart pointer types.

Quote: "You delete the QCPAxis* from the list container.
What happens here is that when QPointer<QCPAxis> loose scope, they also delete the same QCPAxis* (thus the problem).
"

No, no once again, please see the official documentation of the QPointer destructor:
"Destroys the guarded pointer. Just like a normal pointer, destroying a guarded pointer does not destroy the object being pointed to." -- So You can have raw pointers hanging around and QPointers (like in the case of the QCPAxisRect::mAxes vs e.g. mRangeDragHorzAxis) and everything is safe as long as the raw pointer is the owner (and deletes), and the QPointer is only a user but never deletes (because otherwise the raw pointer would become dangling). This is why mRangeDragHorzAxis must be a smart pointer of QPointer type, because it must know, before being used internally, that the respective axis wasn't at some point deleted. If it were a raw pointer, you would have a dangling pointer in that case and crash. Of course you could write code that upon deletion of axes all mRangeDragHorzAxis that might or might not hold that specific axis get cleared, but then you're just writing your own smart pointer from scratch, so better use QPointer right away which takes care of that.

Now to the interesting part. You see in the qDebug test above that in your implementation indeed QPointer seems to not be doing its job correctly. This is a serious problem and I'll file a bug report for Qt. Please send me via email your minimal project, which just creates and then deletes the QCustomPlot, including the qDebug modification of the QCPAxisRect destructor.
I will post the link to the bug report here once I have all the info.

I see. Understood.
Kind of humorous how this turned out.
Looked at it from the Windows/Visual Studio perspective things looked crazy. What was Emanuel thinking here?!
In the end your widget is still wunderbar of course.

My setup is complicated.
Again platform: Windows 8.1, Visual Studio 2015, 32bit target, Qt 5.4.1
And my personal project is incorporating your QCP into a DLL plug-in (and yes I made sure I was using the same CRT run-times).
I didn't build the Qt binaries and export library used my self, although I know it was build with VS2015 while I think the official version for 5.4.1 is VS2013.

Other than that it's still just:

customPlot = new QCustomPlot();
delete customPlot;

I will make in a VM the official windows environment with 5.4.1 and see if it still has the issue. And then try 5.6 as well with your QCP examples over the week (a lot of downloads and installs) and see how it works there.

Okay I've tested all examples over:
QT 5.6 w/Visual Studio 2015
Qt 5.4.1 w/Visual Studio 2013 (since 2013 is the last supported version)

And had no problems what so ever. No memory corruption, multiple delete, issues.
Getting in the QCPAxisRect::~QCPAxisRect() tests the correct results:

before:
false false
false false
after:
true true
true true

This pretty much proves that the problem is not with Visual Studio. Either it's in the customized build (by someone/company, not me) that I'm using, or pebkac ("problem exists between keyboard and chair").
So far it looks like the ref count when a QPointer is it is zero and this causes some bad chain of events. At any rate I will look at the internals of it as I side by debug in two VMs and report issue to the party that maintains this build.

Thanks for your time and patience. Hopefully something productive out of this in the end.

Solved

There were two problems. None directly having to do with actual QCP code.
This might be the solution for the OP and other posters as well.

1) Visual Studio setup.
Under "Additional Include Directories" add the entry "$(QTDIR)\mkspecs\win32-msvc2013".
Since my setup uses 5.4.1 there is no "..msvs2015" one, but then in either case they point to a "..msvc2005" include file anyhow.
Under the "C/C++" configuration, "Command Line" add under "Additional Options":
"-Zm200 -Zc:strictStrings -Zc:throwingNew"
With these settings the bizarre QPointer behavior went away. Now connected internally to the derived QObject class destructor so when the object pointed to the QPointer gets deleted it's value becomes NULL. And no longer tries to delete the "value" when QPointer's destructor is called.
A strange one.. yea.


2) Had different C Run-time Library (CRT) in debug vs release builds.
In my Qt 5.4.1 setup we are not given the debug versions of the Qt libraries.
I.E For release you have the DLL and import library pair "Qt5Core.dll Qt5Core.lib" and for debug you'ed normally want "Qt5Cored.dll Qt5Cored.lib", with the appended 'd' for "debug".
While most of QPointer code exists in headers there is at least one part that is binary. A "getAndRef" constructor function out of "Qt5Core.dll" gets called from QtSharedPointer::ExternalRefCountData.
See QPointer uses a QWeakPointer internally which in turn borrows QtSharedPointer's data container.
There is very little code in this getAndRef function but unfortunately has a new() in it. So when the ~QPointer() gets hit it's delete/free() hits the debug version and shows up as an error.
I rarely use things like the "boost" library but I see the merits of having a header only version of such things.
The right way to fix this is to use the debug version of the Qt library of course, but if you don't have it you can do some hack like go into "namespace QtSharedPointer", "struct ExternalRefCountData" and put a switch around the delete function like:

#ifdef _DEBUG
inline void operator delete(void *ptr) { /*::operator delete(ptr);*/ } // Commented out delete to bypass CRT issue
#else
inline void operator delete(void *ptr) { ::operator delete(ptr); }
#endif
So the destructor never gets called in debug builds, it'll just make a 12 byte memory leak though that you probably won't notice while debugging anyhow.


Oh the joys of trying to get large C/C++ components to work together..

Hey Kevin, great work debugging that toolchain! Glad to hear it's resolved (and wasn't QCP's fault ;))