Known causes for garbage text:
- Using %s with CString argument, without casting to LPCTSTR, may sometime generate garbage text, depending on multiple scenarios. See the explanations below.
- Using incorrect %s or %S specifier.
Use (LPCTSTR) cast with CString argument:
- CString::Format with %s specifier does NOT expect the matching argument to be a CString.
- CString::Format internally uses vsprintf, so the %s specifier expects the argument to be a pointer to a null-terminated string of TCHAR characters, an LPCTSTR to be exact.
- CString objects in Visual Studio 2003 (MFC 7.1) and later "automagically" dereference themselves to the internal string data.
- CString objects in Visual Studio and eMbedded VC++ versions prior to 2003 do NOT dereference themselves to the internal string data.
- Sub-classed CString objects containing virtual functions, regardless of which version of Visual Studio is used, do NOT dereference themselves to the internal string data, they dereference to their v-table. Thanks Michael Walz
- This means that if you sub-class CString and your custom class contains any virtual functions, or you are using Visual Studio or eMbedded VC++ versions prior to 2003, you must use a pointer to the internal string data of the CString argument.
- The only 100% reliable way to get a pointer to the internal string data of a CString object is to either cast to LPCTSTR, use GetBuffer( 0 ), or use GetString().
The official MSDN documentation on CString::Format has something completely wrong to say about this:
When you pass a character string as an optional argument, you must cast it explicitly as LPCTSTR.
This is NOT correct. It should read "When you pass a CString object as an optional argument, you must cast it explicitly as LPCTSTR."
- A CString is not the same thing as a "character string".
- If you pass a character string (char or wchar) as an optional argument, and it matches the project's character-set setting, then the character string is already an LPCTSTR as far as printf related functions are concerned. Casting a character string to LPCTSTR is completely unnecessary.
- It is the CString argument which needs to be cast to LPCTSTR.
Correct Usage of LPCTSTR cast for CString argument:
CString First( _T("John") ); // CString or sub-class CString Last( _T("Doe") ); // CString or sub-class CString Name; Name.Format( _T("%s %s"), (LPCTSTR)First, (LPCTSTR)Last ); //or Name.Format( _T("%s %s"), First.GetString(), Last.GetString() ); //or Name.Format( _T("%s %s"), First.GetBuffer(0), Last.GetBuffer(0) );
Incorrect Usage: don't do this:
CString First( _T("John") ); // CString or sub-class CString Last( _T("Doe") ); // CString or sub-class CString Name; Name.Format( _T("%s %s"), First, Last ); // Name may be garbage text
Use correct %s or %S specifier:
- You must use the correct %s or %S specifier, based on the argument type (CHAR, WCHAR, or TCHAR) and project character-set type (Unicode or ANSI).
|String Type||ANSI Projects||Unicode Projects|
|CString or TCHAR*||%s (lowercase s)||%s (lowercase s)|
|CStringA or char*||%s (lowercase s)||%S (uppercase s)|
|CStringW or WCHAR*||%S (uppercase s)||%s (lowercase s)|
- What if you plan to change the project's character set setting? My suggestion would be to use %s with an intermediate CString variable cast to LPCTSTR. Read more about this here.
- Update 1/16/2013: I've always been aware of a problem with using %s with CString::Format with CString arguments, but I never bothered to track down the cause of the problem, because casting to an LPCTSTR seemed to be an acceptable solution. The answer came as part of a discussion about this article on stackoverflow.com. Michael Walz properly identified that the problem was only with sub-classed objects derived from classes containing virtual functions, because the object no longer "magically" dereferences itself to its internal string data, it dereferences itself to its v-table. I have since rewritten this article to include this updated information.