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:
- Das Projekt ist ein Unicode Projekt.
- 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.
- 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.
- 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 Kommentare zu “Die Cx2y Falle…”
Link für diesen Beitrag | RSS-Feed zu diesem Beitrag
Hinterlassen sie einen Kommentar:
Beachten sie bitte, dass Kommentare evtl. nicht sofort hier erscheinen. Die Kommentare werden zur Moderation an den Webmaster gesendet. Es kann also etwas dauern, bis Ihr Kommentar hier veröffentlicht wird!
on Fr 23 Nov 2007 um 09:16 #
triendl.kj
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());
on Fr 23 Nov 2007 um 15:12 #
Martin Richter
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
on Fr 23 Nov 2007 um 16:38 #
triendl.kj
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…
on Fr 23 Nov 2007 um 18:52 #
Martin Richter
Ich habe das ganze unter VC-2005 gemacht und es geht nicht…
Ich vermute es liegt daran, dass es seit VC-2005 auch __FUNCTIONW_ gibt. Dieses Token liefert einen Unicode String
on Sa 24 Nov 2007 um 16:45 #
triendl.kj
Bei mir gehts unter VC-2005 Express genauso, gleich wie unter 2003
on Di 08 Jan 2008 um 22:06 # Martin’s Blog » Die Unsitte Objekte direkt in printf und Funktionen mit variabler Anzahl von Argumenten zu nutzen
[...] kleine Änderung, und schon geht die Sache in die Hose. Das habe ich ja schon in dem Artikel Die Cx2y Falle beschrieben. Oder sollte also so etwas wie eine Änderung der Implementierung von CString [...]