V745. A 'wchar_t *' type string is incorrectly converted to 'BSTR' type string.
Анализатор обнаружил, что со строкой типа "wchar_t *" начинают работать как со строкой типа BSTR. Это очень подозрительно, и код, скорее всего, содержит ошибку. Чтобы лучше понять, в чем заключается опасность, сначала вспомним, что такое BSTR.
На самом деле, мы процитируем статью с сайта MSDN. Читать MSDN документацию не любят, но это нужно сделать. Важно понять, в чем заключается беда, так как предупреждение V745 часто сигнализирует о серьезных ошибках.
typedef wchar_t OLECHAR;
typedef OLECHAR * BSTR;
BSTR (basic string или binary string) – это строковый тип данных, который используется в COM, Automation и Interop функциях. Тип BSTR следует использовать во всех интерфейсах. Представление BSTR:
- Префикс длины. Целое число размером 4 байта, которое отображает длину следующей за ним строки в байтах. Префикс длины указывается непосредственно перед первым символом строки и не учитывает символ-ограничитель.
- Строка данных. Строка символов в кодировке Unicode. Может содержать множественные вложенные нулевые символы.
- Ограничитель. Два нулевых символа.
Тип BSTR является указателем, который указывает на первый символ строки, а не на префикс длины.
Память для BSTR-строк выделяется с помощью функций выделения памяти COM, поэтому они могут возвращаться методами без необходимости контроля над выделением памяти.
Представленный ниже код является неправильным:
BSTR MyBstr = L"I am a happy BSTR";
Данный пример собирается (компилируется и линкуется) успешно, но не будет работать должным образом, поскольку у строки отсутствует префикс длины. Если проверить расположение в памяти данной переменной с помощью отладчика, он покажет отсутствие префикса длины размером 4 байта перед началом строки данных.
Правильный вариант кода должен выглядеть так:
BSTR MyBstr = SysAllocString(L"I am a happy BSTR");
Теперь отладчик покажет наличие префикса длины, который равен значению 34. Оно соответствует 17 символам, которые приводится к wide-character строке с помощью строкового модификатора "L". Отладчик также покажет двухбайтовый символ-ограничитель (0x0000) в конце строки.
Если передать простую Unicode-строку в качестве аргумента функции COM, которая ожидает BSTR-строку, произойдет сбой в работе этой функции.
Надеюсь, процитированного фрагмента MSDN достаточно, чтобы понять, почему следует разделять BSTR и простые строки типа "wchar_t *".
Также надо понимать, что анализатор не может точно предсказать, есть в коде настоящая ошибка или нет. Если неправильная BSTR строка передается куда-то вовне, то произойдёт сбой. Если же BSTR строка превращается обратно в "wchar_t *", то всё хорошо. Имеется в виду код вида:
wchar_t *wstr = Foo();
BSTR tmp = wstr;
wchar_t *wstr2 = tmp;
Да, здесь нет настоящей ошибки. Но это "код с запахом". И его следует поправить. Так он вызовет меньше недоумения у программиста, сопровождающего код, и анализатор не будет выдавать предупреждение. Следует использовать правильные типы данных:
wchar_t *wstr = Foo();
wchar_t *tmp = wstr;
wchar_t *wstr2 = tmp;
Рекомендуем также ознакомиться со ссылками, приведёнными в конце статьи. Они помогут разобраться с BSTR-строками и тем, как их можно конвертировать в строки других типов.
Рассмотрим ещё один пример:
wchar_t *wcharStr = L"123";
wchar_t *foo = L"12345";
int n = SysReAllocString(&wcharStr, foo);
Описание функции SysReAllocString:
INT SysReAllocString(BSTR *pbstr, const OLECHAR *psz);
Выделяет новую BSTR и копирует в нее заданную строку, затем освобождает BSTR, на которую указывает pbstr, и помещает по этому адресу указатель на новую BSTR.
Как видите, функция ожидает в качестве первого аргумента указатель на переменную, содержащую адрес строки в формате BSTR. Но вместо этого ей передают указатель на обыкновенную строку. Так как тип "wchar_t **" с точки зрения компилятора то же самое, что и "BSTR *", то этот код успешно компилируется. Но на практике он не имеет смысла и приведёт к ошибке на этапе исполнения.
Правильный вариант кода:
BSTR wcharStr = SysAllocString(L"123");
wchar_t *foo = L"12345";
int n = SysReAllocString(&wcharStr, foo);
Дополнительно рассмотрим ситуацию, когда используется ключевое слово 'auto'. Анализатор выдаёт предупреждение на следующий безобидный код:
auto bstr = ::SysAllocStringByteLen(foo, 3);
ATL::CComBSTR value;
value.Attach(bstr); // Warning: V745
Да, это ложное срабатывание, но формально анализатор прав, выдавая предупреждение. Переменная 'bstr' имеет тип 'wchar_t *'. Компилятор языка C++ при выведении типа для auto-переменной не учитывает, что функция возвращает значение типа 'BSTR'. При выведении 'auto', тип 'BSTR' - это просто синоним 'whar_t *'. Получается, что написанный код эквивалентен:
wchar_t *bstr = ::SysAllocStringByteLen(foo, 3);
ATL::CComBSTR value;
value.Attach(bstr);
Поэтому анализатор PVS-Studio и выдаёт предупреждение, так как нехорошо хранить указатель на 'BSTR' строку в обыкновенном 'wchar_t *' указателе. Чтобы устранить предупреждение, следует отказаться в данном месте от 'auto' и написать тип явно:
BSTR *bstr = ::SysAllocStringByteLen(foo, 3);
ATL::CComBSTR value;
value.Attach(bstr);
Это интересный случай, когда оператор 'auto' не помогает, а наоборот, теряет информацию о типе и ухудшает ситуацию.
Другой вариант устранить предупреждение - использовать один из механизмов подавления ложных срабатываний, описанных в документации.
Дополнительные ссылки:
- MSDN. BSTR.
- StackOverfow. Static code analysis for detecting passing a wchar_t* to BSTR.
- StackOverfow. BSTR to std::string (std::wstring) and vice versa.
- Robert Pittenger. Guide to BSTR and CString Conversions.
Данная диагностика классифицируется как:
Взгляните на примеры ошибок, обнаруженных с помощью диагностики V745. |