16
class MyString
{
public:
    MyString(const std::wstring& s2)
    {
        s = s2;
    }

    operator LPCWSTR() const
    {
        return s.c_str();
    }
private:
    std::wstring s;
};

int _tmain(int argc, _TCHAR* argv[])
{
    MyString s = L"MyString";
    CStringW cstring = L"CString";
    wprintf(L"%s\n", (LPCWSTR)cstring); // Okay. Becase it has an operator LPCWSTR()
    wprintf(L"%s\n", cstring); // Okay, fine. But how?        
    wprintf(L"%s\n", (LPCWSTR)s); // Okay. fine.
    wprintf(L"%s\n", s); // Doesn't work. Why? It prints gabage string like "?."
    return 0;
}

How can CString be passed to format string %s?

By the way, MSDN says(it's weird)

To use a CString object in a variable argument function
Explicitly cast the CString to an LPCTSTR string, as shown here:

CString kindOfFruit = "bananas";
int      howmany = 25;
printf( "You have %d %s\n", howmany, (LPCTSTR)kindOfFruit ); 
Benjamin
  • 10,085
  • 19
  • 80
  • 130

4 Answers4

16

CString is specifically designed such that it only contains a pointer that points to the string data in a buffer class. When passed by value to printf it will be treated as a pointer when seeing the "%s" in the format string.

It originally just happened to work with printf by chance, but this has later been kept as part of the class interface.


This post is based on MS documentation long since retired, so I cannot link to their promise that they will continue to make this work.

However, before adding more downvotes please also read this blog post from someone sharing my old knowledge:

Big Brother helps you

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
9
    wprintf(L"%s\n", (LPCWSTR)cstring); // Okay. It's been cast to a const wchar_t*.
    wprintf(L"%s\n", cstring); // UNDEFINED BEHAVIOUR
    wprintf(L"%s\n", (LPCWSTR)s); // Okay, it's a const wchar_t*.
    wprintf(L"%s\n", s); // UNDEFINED BEHAVIOUR

The only thing you can pass to this function for %s is a const wchar_t*. Anything else is undefined behaviour. Passing the CString just happens to work.

There's a reason that iostream was developed in C++, and it's because these variable-argument functions are horrifically unsafe, and shoud never be used. Oh, and CString is pretty much a sin too for plenty of reasons, stick to std::wstring and cout/wcout wherever you can.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 3
    The undefined behavior for CString is actually implementation specific behavior. Microsoft supports this usage. – Bo Persson Jul 07 '11 at 10:57
  • 2
    @BoP it's undefined behavior in C++, because it is undefined behavior in C's standard library `printf` documentation. That microsoft gives it a meaning (I have yet to see an official documentation that does, though) is fine, but the code is still not portable. – Johannes Schaub - litb Jul 07 '11 at 10:58
  • 1
    @Johannes - It sure is, but the question was why it works anyway for CString. – Bo Persson Jul 07 '11 at 10:59
  • 1
    Hmm, the `wprintf` documentation says in my manpage for `%s`: *"If no l modifier is present: The const char * argument is expected to be a pointer to an array of character type "* Looks like the questioner has to use `%ls` instead. – Johannes Schaub - litb Jul 07 '11 at 11:04
  • 2
    @BoP I just want to note that it is undefined behavior. People that are unsure may read your comment as "No, it is not undefined behavior. But it is implementation defined". Which is wrong. I'm aware of what you may have wanted to express. – Johannes Schaub - litb Jul 07 '11 at 11:12
  • @DeadMG passing `wchar_t*` (not const) is also undefined behaviour, though? – Benjamin Apr 10 '12 at 04:55
  • "stick to std::wstring" unless you want to work with someting approximating the modern world - and UniCode! – David V. Corbin May 05 '22 at 14:10
4

CString has a pointer as the first member:

class CStringA
{
      char* m_pString;
};

Though it is not char* (even for ANSI CString), it is more or less the same thing. When you pass CString object to any of printf-family of functions (including your custom implementation, if any), you are passing CString object (which is on stack). The %s parsing causes it it read as if it was a pointer - which is a valid pointer in this case (the data at very first byte is m_pString).

Ajay
  • 18,086
  • 12
  • 59
  • 105
2

Generally speaking it's undefined behavior. According to this article Visual C++ just invokes the conversion from CString to a POD type to cover you - that is permissible implementation of undefined behavior.

sharptooth
  • 167,383
  • 100
  • 513
  • 979