Bug or Feature: Dereferencing QList::begin() segfaults on an empty QList using Qt 6
-
Hi all,
When using iterators on an empty list like this:
QList<int> list; qDebug() << "begin:" << *list.begin(); qDebug() << "end:" << *list.end(); qDebug() << "max_element:" << *std::max_element(list.begin(), list.end());
I get the following compiling that code using Qt 5:
begin: 0 end: 0 max_element: 0
However if I do the same using Qt 6, the above code segfaults when trying to dereference list.begin(). Which is somehow understandable, as there's nothing the iterator could point to …
So … is this an intentional change in Qt 6?
Thanks for all clarification!
-
Hi all,
When using iterators on an empty list like this:
QList<int> list; qDebug() << "begin:" << *list.begin(); qDebug() << "end:" << *list.end(); qDebug() << "max_element:" << *std::max_element(list.begin(), list.end());
I get the following compiling that code using Qt 5:
begin: 0 end: 0 max_element: 0
However if I do the same using Qt 6, the above code segfaults when trying to dereference list.begin(). Which is somehow understandable, as there's nothing the iterator could point to …
So … is this an intentional change in Qt 6?
Thanks for all clarification!
@l3u_ said in Bug or Feature: Dereferencing QList::begin() segfaults on an empty QList using Qt 6:
So … is this an intentional change in Qt 6?
If *list.begin() really did not segfault on Qt5 (which I doubt, maybe it worked in debug mode) this was a bug - the iterator points to nowhere so you can't dereference it. Your usage is simply wrong.
-
Looking at the code, I must admit that I wonder why it worked at all, but it did – not only in debug mode, but also when compiling a release build. I simply didn't notice this until my Qt 6 build crashed here (which is understandable).
So that's actually a Qt 5 bug that has been fixed in Qt 6. Thanks for the confirmation.
-
L l3u_ has marked this topic as solved on
-
Looking at the code, I must admit that I wonder why it worked at all, but it did – not only in debug mode, but also when compiling a release build. I simply didn't notice this until my Qt 6 build crashed here (which is understandable).
So that's actually a Qt 5 bug that has been fixed in Qt 6. Thanks for the confirmation.
@l3u_ said in Bug or Feature: Dereferencing QList::begin() segfaults on an empty QList using Qt 6:
So that's actually a Qt 5 bug
For the record, it is/was not a "bug". As @Christian-Ehrlicher says, you cannot dereference an iterator not pointing to a list element (e.g. from
begin()
on an empty list). I haven't looked it up, but I imagine that would be classed as "Undefined Behaviour". Which means it can result in anything happening, or not happening, no more and no less. If you say it "worked" in Qt5 that behaviour would not be a "bug". A bug would be where defined behaviour failed to work correctly. Undefined behaviour might crash or might work. Nothing wrong with it apparently working in Qt5. Just a "shame" for you because you didn't pick the UB up, but not a bug! -
It actually was defined behavior, because *std::max_element(list.begin(), list.end()) always returned 0 for an empty QList<int>, in each and every case the program ran (and it ran lots of times).
If it returned some random value like you get when you use an unitialized int or such (which would be "undefined behavior"), the following for loop (that used the return value as a <= condition) would have been executed on some random positive value, and the code within would have crashed.
And that's why I never found the programming mistake: I tested the code on a non-empty QList<int>, but I always got 0 for an empty one later on. Well, whatever. I now fixed it.
Maybe this has something to do with a QList being an actual QList in Qt 5 (maybe returning the default value, which would be 0 for a QList<int> for the begin() and end() of an empty list?!), and with Qt 6, a QList is actually a QVector, behaving differently.
-
It actually was defined behavior, because *std::max_element(list.begin(), list.end()) always returned 0 for an empty QList<int>, in each and every case the program ran (and it ran lots of times).
If it returned some random value like you get when you use an unitialized int or such (which would be "undefined behavior"), the following for loop (that used the return value as a <= condition) would have been executed on some random positive value, and the code within would have crashed.
And that's why I never found the programming mistake: I tested the code on a non-empty QList<int>, but I always got 0 for an empty one later on. Well, whatever. I now fixed it.
Maybe this has something to do with a QList being an actual QList in Qt 5 (maybe returning the default value, which would be 0 for a QList<int> for the begin() and end() of an empty list?!), and with Qt 6, a QList is actually a QVector, behaving differently.
@l3u_ said in Bug or Feature: Dereferencing QList::begin() segfaults on an empty QList using Qt 6:
It actually was defined behavior
Could you state what the defined behaviour is/was on dereferencing the
begin()
of an empty list/an iterator not pointing to a valid element? I shall be interested to learn this, as you say at Qt6 at least it seg faults, which is a (possible) symptom of undefined behaviour.I tried to point it that just because some compiler/code happens to have produced repeatable or sensible behaviour on a C++ undefined behaviour does not make it C++ defined behaviour. As witness that it no longer produces the same result now. You can test it as many times as you like and it can perhaps produce the same result every time, doesn't make it not be undefined behaviour.
I don't understand your point at all. Here is the definition of C++ undefined behaviour. I would suggest this one is similar to
Some examples of undefined behavior are [...], memory accesses outside of array bounds, [...] null pointer dereference,
*std::max_element(list.begin(), list.end())
Per the spec
std::max_element()
return last on empty list, i.e. yourlist.end()
. So this is the same as*last.end()
. Which is UB.You always get 0 for both the QList and the QVector when compiled with Qt 5. And always a crash with Qt 6.
And what is your point? It's undefined behaviour. I really don't understand what you are getting at that it happens to work some way under Qt6 and another way under Qt5. That's just the sort of thing that undefined behaviour does :) Though I respect if you are just observing what used to happen versus what happens now, as an observation this is fine. Just that it doesn't make it a "bug" under Qt5. I have said my piece.
-
I'm neither a professional programmer, nor a studied computer scientist. I also already stated I'm surprised why the code worked at all, because It's totally clear to me that dereferencing an iterator pointing to nowhere should segfault, and never return a value – because there's no value to return. That's what Qt 6 does.
I thus concluded that returning a valid value (and always the same) for something that's not there instead of crashing could be considered a bug (at least to some clueless outsider).
Can we simply agree that I wrote shitty code and you're right?
-
I'm neither a professional programmer, nor a studied computer scientist. I also already stated I'm surprised why the code worked at all, because It's totally clear to me that dereferencing an iterator pointing to nowhere should segfault, and never return a value – because there's no value to return. That's what Qt 6 does.
I thus concluded that returning a valid value (and always the same) for something that's not there instead of crashing could be considered a bug (at least to some clueless outsider).
Can we simply agree that I wrote shitty code and you're right?
@l3u_
Fine, and I did not intend to be "on your case". It's just that in C++ "Undefined Behaviour" has a particular meaning. It wasn't a "bug" that Qt5 did what it did, UB does not mean that it must seg fault or similar, it means it can do anything it wants. If you expect it to error when code is wrong/UB you can be lured into a false sense of security, which later comes to bite you back when behaviour changes, exactly as you have found here with Qt5/6. C++ is like that. Other languages might guarantee to give you an error in a similar case, but not C++. It is probably "coincidence" that Qt5 gave you 0 and Qt6 gives a seg fault, rather than it being "deliberate/planned" for Qt6.We are good :)
-
I'm neither a professional programmer, nor a studied computer scientist. I also already stated I'm surprised why the code worked at all, because It's totally clear to me that dereferencing an iterator pointing to nowhere should segfault, and never return a value – because there's no value to return. That's what Qt 6 does.
I thus concluded that returning a valid value (and always the same) for something that's not there instead of crashing could be considered a bug (at least to some clueless outsider).
Can we simply agree that I wrote shitty code and you're right?
Undefined Behavior can also mean that code which "shouldn't work", does 7/10 times what your actual intention was (even though the code is wrong) and crashes 3/10 times.
This still doesn't make the code correct, even if some resulting behavior leads to crashing your program while the other "seem" to work.