V745. A 'wchar_t *' type string is incorrectly converted to 'BSTR' type string.
Анализатор обнаружил, что со строкой типа wchar_t *
начинают работать как со строкой типа BSTR. Это очень подозрительно, и код, скорее всего, содержит ошибку. Чтобы лучше понять, в чем заключается опасность, сначала вспомним, что такое BSTR.
Далее мы процитируем статью с сайта 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-строку, произойдёт сбой в работе этой функции.
Примечание. Анализатор не может точно предсказать, есть в коде настоящая ошибка или нет. Если неправильная 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. |