Die Cx2y Falle…

In einem Programm habe ich diese unscheinbare Schleife für eine spezielle Analyse im Debug-Mode eingebaut.

for (POSITION pos= m_aBind.GetHeadPosition(); pos; )

 S_BIND& b= m_aBind.GetNext(pos);
 TRACE(__FUNCTION__ “  %-32s %2d\n“,
  CT2A(b.sName.GetString()),
  b.GetStatus());
}

Sieht auf den ersten Blick OK aus.
Auf den zweiten Blick wundert einen evtl. die Nutzung von CT2A. Warum nehme ich nicht einfach den Zeiger von GetString wie er ist? Es ist ja ein PCTSTR! Begründung:

  1. Das Projekt ist ein Unicode Projekt. 
  2. CT2A verwende ich hier, weil die TRACE Funktion in dieser Schreibweise nur MBCS Strings verwendet. D.h. %s erwartet einen Zeiger auf einen MBCS String.
  3. Die _T Schreibweise für die Ausgabemaske ist hier nicht geeignet, weil ich gerne den Funktionsnamen auch mit ausgegeben hätte. Und __FUNCTION__ ist nun mal eine normale String-Konstante, und kein wchar_t-String.
  4. T2A verwende ich nicht, weil dies in einer Schleife tödlich ist, weil der Stack immer weiter anwächst.

OK! Soweit dazu, warum der Code aussieht wie er aussieht.

❗ Nur… es kommt nur Schrott dabei heraus!
Die Ausgabe zeigt brav, den Text, den ich haben möchte, nur die Zahlenwerte stimmen in keiner Weise mit den Rückgabewerten von GetStatus überein! Warum? ❓

Was ist passiert? Der Debugger gibt schnell Auskunft:
CT2W erzeugt ein Objekt vom Typ CW2AEX!!! Dieses Objekt ist aber größer als ein PCSTR Zeiger. Dieses Objekt umfasst bei meinen Projekteinstellungen 4 Bytes für einen Zeiger plus einen char-Array der Größe 128. TRACE hätte aber gerne einen 4-Byte Zeiger auf dem Stack und danach für die Ausgabe einen Integer.
Ausgegeben, werden also die 4 ersten Bytes des Puffers von CW2AEX und nicht die gewünschten numerischen Werte für GetStatus!

Das ist übrigens die selbe Falle in die man tritt, wenn man CString direkt ohne CString::GetString in printf/TRACE und Konsorten verwendet. Nur hat man hier das Glück, dass ein CString exakt 4 Bytes groß ist und genau aus einem Zeiger auf einen PTSTR besteht.

Wie macht man es richtig?
Genau… man führt den entsprechenden cast ein. Der cast benutzt nun den cast-Operator der Klasse CW2AEX, und der liefert uns den Zeiger den wir wollten.

for (POSITION pos= m_aBind.GetHeadPosition(); pos; )
{
 S_BIND& b= m_aBind.GetNext(pos);
 TRACE(__FUNCTION__ “ %-32s %2d\n“,
  static_cast<PCSTR>(CT2A(b.sName.GetString())),
  b.GetStatus());
}

Amerkungen:

  • Ich brauche wahrscheinlich nicht zu erwähnen, dass ähnliche Probleme mit CA2W, CA2T etc. auch auftauchen können.
  • An den meisten Stellen an denen, wir diese Konvertierungen, wie z.B. CT2A verwenden wird auch direkt ein PCTSTR erwartet, oder eben ein Zeiger auf den entsprechend konvertieren String. Dadurch wird implizit der Konvertierungs-Operator der Klasse aufgerufen und damit funktioniert ales wie es soll.
  • Auch wenn das Projekt ein MBCS Projekt gewesen wäre, hätte es die selben Probleme verursacht. Denn in diesem Fall wird aus CT2A eine Objekt der Klasse CA2AEX. Im Gegensatz dazu würde T2A einfach zu ener Noop.

6 Gedanken zu „Die Cx2y Falle…“

  1. Der Blogeintrag ist zwar schon etwas älter, nichtsdestotrotz…

    Wieso so kompliziert mit CT2A und static_cast?
    Das Makro TRACE expandiert auf das Makro ATLTRACE welches wiederum nach ATL::CTraceFileAndLineInfo(__FILE__, __LINE__) expandiert. Dies ist dann ein temporäres Funktionsobjekt mit einem Funktionsoperator void __cdecl operator()(const wchar_t *pszFmt, …) const (Unicode) bzw. void __cdecl operator()(const char *pszFmt, …) const, dieser bekommt dann die Parameter, die an TRACE übergeben worden sind. Damit lässt sich TRACE also mit _TCHARs verwenden, was dann auch _T inkludiert.

    TRACE(_T(__FUNCTION__) _T(” %-32s %2d\n”),
    b.sName.GetString(),
    b.GetStatus());

  2. Nein, Dein Code geht nicht!
    _T(__FUNCTION__) funktioniert nicht, weil __FUNCTION__ nicht auf eine char Konstante expandiert, sondern ein eigenes Keyword im Preprozessor ist. Und _T() aus dem __FUNCTION__ ein L__FUNCTION__ macht, was nun ein unbekannter Identifier ist.

    error C2065: ‚L__FUNCTION_‘ : undeclared identifier

  3. Tja,

    bei mir geht das (hab das natürlich vorher getestet) mit VC++ 2003; liegt das an den Projekteinstellungen?.
    Ein Blick in die Ausgabe des Preprozessors sagt, dass _T(__FUNCTION__) nach __LPREFIX(__FUNCTION__) expandiert!

    __LPREFIX scheint wohl ein undokumentiertes Makro zu sein, was das dann genau macht…

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

I accept that my given data and my IP address is sent to a server in the USA only for the purpose of spam prevention through the Akismet program.More information on Akismet and GDPR.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.