How sprintf works with CString and std::string

rmi picture rmi · Feb 26, 2014 · Viewed 7.1k times · Source
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?

Answer

marcinj picture marcinj · Feb 26, 2014

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;
}