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:

CString::Format

About these ads

About Ed Nafziger

Ed is a computer programmer who works on both MFC/C projects and maintaining legacy C code. The majority of projects he works with are either Windows desktop or CE, and he will be sharing some of his trade secrets on these topics.

Posted on March 23, 2012, in C/C++, MFC, MSDN Documentation Errors and tagged , , , , , , , , , , , . Bookmark the permalink. 3 Comments.

  1. 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 ?

  2. 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.

  3. Hi Ed,

    thanks a lot for the clarifications..

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: