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.
REF:
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.
Does anybody know why in this codes we *may* get garbage text :
CString First( “John” );
CString Last( “Doe” );
CString Name;
Name.Format( “%s %s”, First, Last ); // Name may be garbage text
Given a specific compiler and a specific implementation of MFC we should either get garbage *all* the time or a correct result *all* the time.
Am I missing something here ?
Hi Michael:
When I mentioned “may sometimes generate garbage text” I did not mean the issue is intermittent. I meant there are multiple scenarios that could cause garbage text. So depending on the scenario, use of CString::Format may or may not cause garbage text.
The same code should either always generate garbage text or always format properly, depending on unknown factors. Perhaps Unicode or ANSI character-set setting, Visual Studio version, desktop or CE project, or compiler optimization setting, etc.
Thanks.
Hi Ed,
thanks a lot for the clarifications..