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
?
Это работает, потому что первый элемент класса CString является указателем на массив символов. Фактически, единственное поле в CString — это указатель на строковый массив. Этот класс использует некоторые приемы, чтобы скрыть внутренние данные (например, длину строки, зарезервированный размер буфера и т. Д.), Выделяя один большой буфер, а затем оставляя единственный указатель класса, указывающий на массив char, чтобы добраться до этих внутренних полей данных, он смещает этот указатель на известные смещение.
Что вы должны сделать, это вызвать s.GetBuffer (0); или (LPCTSTR) s; но используя его как
sprintf(z2, "%s", ss);
было разрешено, так как по замыслу создателей MFC, конечно, он работает под Windows, на других платформах может произойти сбой.
[редактировать после комментариев]ваш код будет безопаснее, если вместо C-стиля (LPCTSTR)s
вы будете использовать c ++ cast: static_cast<LPCTSTR>(s);
, Но очень скоро вы обнаружите, что ваш код становится уродливым со всеми этими static_cast-s, особенно если у ваших sprintf-s много параметров. Это, насколько я помню (и, на мой взгляд), дизайн, приведения в стиле c ++ предназначены для того, чтобы вы переосмыслили свой дизайн, чтобы вообще не использовать приведения. В вашем случае вместо использования 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;
}
Это поведение 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. Я бы использовал C ++ — стиль приведения static_cast<PCWSTR>(string)
или просто 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, в котором обсуждается эта проблема.