How sprintf works with CString and std::string
Asked Answered
D

2

9
CString s = "test";
std::string ss = "test";

char z[100];
sprintf(z, "%s", ss.c_str()); // z = "test"  : OK

char z2[100];
sprintf(z2, "%s", ss); // z2 = "(null)" : OK. undefined behavior is expected

char z3[100];
sprintf(z3, "%s", s); // z3 = "test" : How is this possible ?!

Can anybody explain how CString works properly with sprintf?

Dorr answered 26/2, 2014 at 9:20 Comment(0)
I
9

It works because the first element of CString class is a pointer to char array. Actually, the only field in CString is a pointer to a string array. This class uses some tricks to hide internal data (like string length, reserved buffer size, etc) by allocating one big buffer and then leaving the only class pointer pointed to char array, to get to those internal data fields it shifts this pointer by known offset.

What you should do is call s.GetBuffer(0); or (LPCTSTR)s; but using it as

sprintf(z2, "%s", ss);

was allowd as by design of MFC creators, of course it works under Windows on other platforms it might crash.

[edit after comments]

your code will be safer if instead of c-style casts like (LPCTSTR)s you will use c++ cast: static_cast<LPCTSTR>(s);. But very soon you will find out that your code gets ugly with all this static_cast-s, especially if your sprintf-s have lots of parameters. This is as far as I remember (and in my opinion) by design, c++ style casts are designed to make you rethink your design to not use casts at all. In your case instead of using sprintf you should use std::wstringstream (assuming you use UNICODE build):

#include<sstream>

std::wostream & operator<< (std::wostream &out, CString const &s) {
  out << s.GetString();
  return out;
}

int main(){
  CString s = _T("test");
  std::wstringstream ss;
  ss << s;  // no cast required, no UB here
  std::wcout << ss.str();
  return 0;
}
Israelite answered 26/2, 2014 at 9:36 Comment(4)
The correct method to call is CString::GetString(), not GetBuffer(). Note also that C-style casts are bad: please use C++-style casts: static_cast<LPCTSTR>(ss).Parsec
To be more precise, in your case the cast should be static_cast<const char*>(ss), since you are using sprintf() (not a TCHAR-based function like _stprintf_s()). In this way, on Unicode builds (which have been the default since VS2005), where CString is actually CStringW, the cast fails and the code doesn't compile, and you can fix it (instead of passing a wrong argument to sprintf(), and silently introducing a bug at run-time).Parsec
ss is the std::string in the example, ITYM s.GetBuffer(0) etc. It would be ss.c_str() otherwise.Sailboat
thanks for comments, I have included them in my edit. I am not sure if OP is using UNICODE or not, CString as far as I can remember, can implicitly convert multibyte to wchar_t string when its assigned to it.Israelite
P
6

This behavior of CString seems not officially supported by Microsoft (it relies on implementation details of CString, which seem crafted to work in cases like the one you cited, but may change in the future).

Note that MSDN documentation of CString PCXSTR cast operator reads:

// If the prototype isn't known or is a va_arg prototype, 
// you must invoke the cast operator explicitly. For example, 
// the va_arg part of a call to swprintf_s() needs the cast:

swprintf_s(sz, 1024, L"I think that %s!\n", (PCWSTR)strSports);

Actually that cast is bad, since it's a C-style cast. I'd use C++-style cast static_cast<PCWSTR>(string) or just the CString::GetString() method instead.

Another MSDN documentation page reads (emphasis mine):

Using CString Objects in Variable Argument Functions

Some C functions take a variable number of arguments. A notable example is printf_s. Because of the way this kind of function is declared, the compiler cannot be sure the type of the arguments and cannot determine which conversion operation to perform on each argument. Therefore, you must use an explicit type cast when you pass a CString object to a function that takes a variable number of arguments. To use a CString object in a variable argument function, explicitly cast the CString to an LPCTSTR string, as shown in the following example.

CString kindOfFruit = _T("bananas");
int howmany = 25;
_tprintf_s(_T("You have %d %s\n"), howmany, (LPCTSTR)kindOfFruit);    

Again, I do prefer C++-style static_cast<PCTSTR> to the C-style cast used in the MSDN documentation. Or calling CString::GetString() will do just fine.


See also this blog post: Big Brother helps you.

And another thread on StackOverflow in which this issue is discussed.

Parsec answered 26/2, 2014 at 10:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.