Immer wieder sieht man Code wie diesen:
CString strText = _T("xyz");
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"),
strText, 4711);
Sieht harmlos aus und funktioniert. Warum eigentlich?
Es funktioniert nur aus einem einzigen Grund:
CString ist 4 Bytes groß, besteht nur aus einem einzigen Element und das ist ein Zeiger auf einen TCHAR Array!
Das ganze funktioniert sofort nicht mehr wenn wir eine eigene CMyString Klasse ableiten, der wir eine virtuelle Funktion zuordnen. Was passiert nun?
Nun ist CString nicht mehr 4 Bytes groß sondern 8 Bytes und besteht aus zwei Zeigern. Einem Zeiger auf eine vtable und einen Zeiger auf einen TCHAR Array.
Nicht nur wird jetzt kein Text mehr ausgegeben, sondern auch die Zahl wird nicht korrekt ausgegeben! Das ganze obwohl CMyString immer noch einen Umwandlungsoperator für LPCTSTR hat.
Das Problem ist aber, dass _tprintf eine Variable Anzahl von Argumenten (Ellipsis) hat und der C/C++ Compiler gar nicht weiß, was _tprintf erwartet. Also wird das ganze Objekt auf den Stack geschoben.
Man macht eine 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 erfolgen würde solch ein Programm auch sofort nicht mehr funktionieren. Man muss jedoch vermuten, dass Microsoft sich das gar nicht erlauben würde, weil zu viele Entwickler solchen nicht portablen und unabhängigen Code verwenden.
Wie macht man es richtig? Man verwendet den entsprechenden cast oder eine Funktion, die den entsprechenden Typ returniert.
CString strText = _T("xyz");
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"),
static_cast<LPCTSTR> (strText), 4711);
// -- oder --
_tprintf(_T("Irgendwas ist %s und jetzt kommt eine Zahl %d"),
strText.GetString(), 4711);
Man sollte grundsätzlich bei der Verwendung von einer variablen Anzahl von Argumenten (Ellipsis) immer auf den Typ casten, der auch letzten Endes erwartet wird.
Wieder einmal ein Klassiker! Ich erinner mich auch, nach einem (ich glaube sogar Deinem) Hinweis in der Newsgroup, stundenlang durch meinen Sourcecode gestolpert zu sein, um solche Schwachstellen auszumerzen. Okay, ich habe immer den direkten Cast genommen, statt static_cast, aber… 🙂
„Man muss jedoch vermuten, dass Microsoft sich das gar nicht erlauben würde, weil zu viele Entwickler solchen nicht portablen und unabhängigen Code verwenden.“
Das stimmt nicht ganz: wenn eine Applikation für x64 kompiliert wird, dann funktioniert das auch nicht mehr. Es ist also schon jetzt nicht ‚portabel‘, zumindest nicht was win32 und x64 angeht.