Как sprintf работает с CString и std::string

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 ?!

Кто-нибудь может объяснить, как CString правильно работает с sprintf?


person rmi    schedule 26.02.2014    source источник


Ответы (2)


Это работает, потому что первый элемент класса CString является указателем на массив символов. На самом деле единственное поле в CString — это указатель на массив строк. Этот класс использует некоторые приемы, чтобы скрыть внутренние данные (такие как длина строки, зарезервированный размер буфера и т. д.), выделяя один большой буфер, а затем оставляя единственный указатель класса, указывающий на массив символов, чтобы добраться до этих внутренних полей данных, он сдвигает этот указатель на известное значение. компенсировать.

Что вам нужно сделать, так это вызвать s.GetBuffer(0); или (LPCTSTR); но используя его как

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

был разрешен по замыслу создателей MFC, конечно, он работает под Windows, на других платформах может произойти сбой.

[править после комментариев]

ваш код будет безопаснее, если вместо приведений в стиле C, таких как (LPCTSTR)s, вы будете использовать приведение C++: static_cast<LPCTSTR>(s);. Но очень скоро вы обнаружите, что ваш код становится некрасивым со всеми этими static_cast-ами, особенно если ваши sprintf-ы имеют много параметров. Насколько я помню (и, на мой взгляд), по дизайну приведения в стиле С++ предназначены для того, чтобы вы переосмыслили свой дизайн, чтобы вообще не использовать приведения. В вашем случае вместо использования sprintf вы должны использовать std::wstringstream (при условии, что вы используете сборку UNICODE):

#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;
}
person marcinj    schedule 26.02.2014
comment
Правильный метод для вызова: CString::GetString(), не GetBuffer(). Также обратите внимание, что приведение типов в стиле C — это плохо: используйте приведения в стиле C++: static_cast<LPCTSTR>(ss). - person Mr.C64; 26.02.2014
comment
Чтобы быть более точным, в вашем случае приведение должно быть static_cast<const char*>(ss), так как вы используете sprintf() (а не функцию на основе TCHAR, такую ​​​​как _stprintf_s()). Таким образом, в сборках Unicode (которые используются по умолчанию с VS2005), где CString на самом деле CStringW, приведение завершается ошибкой и код не компилируется, и вы можете это исправить (вместо того, чтобы передавать неверный аргумент в sprintf(), и молча вводя ошибку во время выполнения). - person Mr.C64; 26.02.2014
comment
ss — это std::string в примере, ITYM s.GetBuffer(0) и т. д. В противном случае было бы ss.c_str(). - person MSalters; 26.02.2014
comment
спасибо за комментарии, я включил их в свое редактирование. Я не уверен, использует ли OP UNICODE или нет, насколько я помню, CString может неявно преобразовывать многобайтовую строку в строку wchar_t, когда она ему назначена. - person marcinj; 26.02.2014

Похоже, такое поведение CString не официально поддерживается Microsoft (оно основано на деталях реализации CString, которые, похоже, созданы для работы в случаях, подобных приведенному вами, но могут измениться в будущее).

Обратите внимание, что документация MSDN по CString PCXSTR оператору приведения гласит:

// 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);

На самом деле этот актерский состав плохой, так как это актерский состав в стиле C. Вместо этого я бы использовал приведение static_cast<PCWSTR>(string) в стиле C++ или просто метод CString::GetString().

Другая страница документации MSDN гласит: (выделено мной):

Использование объектов CString в функциях с переменными аргументами

Некоторые функции C принимают переменное количество аргументов. Ярким примером является printf_s. Из-за того, как объявляется функция такого типа, компилятор не может быть уверен в типе аргументов и не может определить, какую операцию преобразования выполнять для каждого аргумента. Поэтому вы должны использовать явное приведение типов при передаче объекта CString в функцию, которая принимает переменное количество аргументов. Чтобы использовать объект CString в функции с переменным аргументом, явно приведите CString к строке LPCTSTR, как показано в следующем примере.

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

Опять же, я предпочитаю стиль C++ static_cast<PCTSTR> приведению в стиле C, используемому в документации MSDN. Или позвоните по номеру CString::GetString().


См. также эту запись в блоге: Большой Брат поможет вам.

И еще одна тема на StackOverflow, в которой обсуждается этот вопрос.

person Mr.C64    schedule 26.02.2014
comment
Спасибо за подробное объяснение по кастам. - person rmi; 26.02.2014