CString::Format with %s specifier = garbage text
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, 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 (including a virtual destructor), or you are using Visual Studio or eMbedded VC++ versions prior to 2003, you must use (LPCTSTR) cast with CString arguments.
- Even if you use Visual Studio 2003 and later, and do not use sub-classed CString classes containing virtual functions, consider the following scenarios:
- You need to use new code in an old Visual C++ 6 project. The code uses CString::Format. You now must check every call to CString::Format to see if a CString oject is being passed in, and add the (LPCTSTR) cast.
- What if in the future you must use a custom CString class? and that class contains virtual functions? And the code that will use your custom class uses CString::Format? You now must check every call to CString::Format to see if a custom CString object is being passed in, and add the (LPCTSTR) cast.
- 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.
Using C++ style cast vs C style cast:
- For most scenarios, C++ style casts are recommended and should be used instead of C style casts.
- For example: use static_cast<LPCTSTR>( Object ) instead of (LPCTSTR)Object.
- However, CString::Format internally uses vsprintf, which does not do any type checking of its arguments, and the argument types are not checked by the compiler, so using C++ style static_cast<LPCTSTR>( Object ) does not make any difference (no improvement in terms of type safety), compared to using C style (LPCTSTR)Object casts.
- I usually use C style casts in my examples for brevity, not because I recommend them instead of C++ style casts. Do what you feel is best.
Correct Usage of LPCTSTR cast for CString argument:
CString First( "John" ); // CString or sub-class CString Last( "Doe" ); // CString or sub-class CString Name; Name.Format("%s %s", (LPCTSTR)First, (LPCTSTR)Last );
Incorrect Usage: don’t do this
CString First( "John" ); // CString or sub-class CString Last( "Doe" ); // CString or sub-class CString Name; Name.Format( "%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).
|c-string Type||ANSI Projects||Unicode Projects|
|TCHAR||%s (lowercase s)||%s (lowercase s)|
|CHAR||%s (lowercase s)||%S (uppercase s)|
|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.
Posted on March 23, 2012, in C/C++, MFC, MSDN Documentation Errors and tagged %s, arg, cast, corrupt, CString, Format, garbage, junk, LPCTSTR, printf, specifier, sprintf. Bookmark the permalink. 3 Comments.