4

Definition of qPrintable:

#  define qPrintable(string) QString(string).toLocal8Bit().constData()

toLocal8Bit returns a QByteArray. Documentation of QByteArray::constData():

The pointer remains valid as long as the byte array isn't reallocated or destroyed.

toLocal8Bit() creates a temporary QByteArray, which is destroyed just after constData() has been called. So the pointer returned by constData points to freed memory.

Am I missing something here?

Additional comments: Documentation of qPrintable states this:

The char pointer will be invalid after the statement in which qPrintable() is used. This is because the array returned by QString::toLocal8Bit() will fall out of scope.

But what does this mean? For my understanding, this is identical to "The pointer is invalid from the moment it is returned by qPrintable"

Why I am asking:

I have seen code failing which can be simplified like this:

someFunction( stringA.toLatin1().constData(), stringB.toLatin1().constData())

Inside the called function, both parameters were pointers to the same address.

philipp
  • 1,745
  • 1
  • 14
  • 25

3 Answers3

5

qPrintable macro is supposed to be used for debugging purposes:

qWarning("%s: %s", qPrintable(key), qPrintable(value));

or:

qDebug() << "My value is: " << qPrintable(value);

Here temporary QByteArray object created by QString::toLocal8Bit is destroyed after the whole statement finishes, which happens after QByteArray::constData() pointer is utilized.

And in this case we have a problem you have described:

QString s = "Hello";
const char *debug_string = s.toLocal8Bit().constData();

// Can't use debug_string here as the pointer is invalid:
// qDebug() << debug_string
hank
  • 9,553
  • 3
  • 35
  • 50
2

For my understanding, this is identical to "The pointer is invalid from the moment it is returned by qPrintable"

No. Temporary objects are destroyed at the end of the full expression they're part of, unless their life is prolonged by taking a const reference. See this question for details. The pointer is invalid as soon as the full expression ends.

In a call like foo(obj1().method(), obj2().method()), the entire call is a full expression.

So, this will work:

#include <QtCore>

struct Test {
   Test() { qDebug() << "created"; }
   ~Test() { qDebug() << "destroyed"; }
   Test * fun() { return this; }
};

void f1(Test *, Test*) { qDebug() << "f1"; }

int main()
{
   f1(Test().fun(), Test().fun());
}

Output:

created
created
f1
destroyed
destroyed

I have seen code failing which can be simplified like this: someFunction( stringA.toLatin1().constData(), stringB.toLatin1().constData()). Inside the called function, both parameters were pointers to the same address.

The "simplifications" do matter. As soon as you pull each qPrintable out of the full function call expression where they are used as arguments, things fail:

int main()
{
   auto a1 = Test().fun();
   // here a1 dangles
   auto a2 = Test().fun();
   // here both a1 and a2 dangle 
   f1(a1, a2); // undefined behavior
}

Output:

created
destroyed
created
destroyed
f1

Is qPrintable prone to access violations?

That depends on how aware one is of the semantics of C++. Given that it's an obsolete and unnecessary construct, I'd simply say: prone to undefined behavior or not, there's no reason to use it. If you need to pass a QString to something expecting const char *, be explicit about it in an adapter.

void foo(const char *); // we need to call this with a QString
void foo2(const char *, const char *);

inline void foo(const QString & arg1) { // now we can
  auto a1 { arg1.toLocal8Bit() };
  foo(a1.constData()); // a1's lifetime explicitly extends past the call
}

// or if you really like one-liners
inline void foo2(const QString & arg1, const QString & arg2) {
  foo2(arg1.toLocal8Bit().constData(), arg2.toLocal8Bit().constData());
}
Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
0

But what does this mean? For my understanding, this is identical to "The pointer is invalid from the moment it is returned by qPrintable"

I had the same question, but the key to understanding this is noticing that qPrintable is a macro, not a function:

#ifndef qPrintable
#  define qPrintable(string) QtPrivate::asString(string).toLocal8Bit().constData()
#endif

(from here).

If it were a function, you'd be right. Being a macro, there is no return to speak of. The documentation says "returns", but that is just imprecise. Thus, the scope of the temporary is the scope where qPrintable is invoked.

ricab
  • 2,697
  • 4
  • 23
  • 28