この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。
30. Visual C++* と wprintf() 関数
Energy Checker SDK プロジェクトから抜粋した以下のコードについて考えてみます。このコードにはエラーが含まれています。PVS-Studio アナライザーは、次の診断を出力します。
V576 Incorrect format. Consider checking the second actual argument of the ‘wprintf’ function. The pointer to string of wchar_t type symbols is expected. (V576 不正な形式です。’wprintf’ 関数の第 2 実引数を確認してください。wchar_t 型のシンボルの文字列へのポインターが想定されます。)
int main(void) { ... char *p = NULL; ... wprintf( _T("Using power link directory: %s\n"), p ); ... }
説明
注: 最初のエラーは、ワイド文字形式の文字列の指定に _T を使用しているためです。ここでは、L プリフィクスを使用します。しかし、これは重大なミスではなく、注目に値しません。ワイド文字形式を使用しないと _T は展開されず、コードがコンパイルされないだけです。
wprintf() 関数で char* 型の文字列を出力するには、書式設定文字列で “%S” を使用すべきです。
多くの Linux* プログラマーは、この落とし穴に気付かないでしょう。Microsoft* は、wsprintf などの関数を奇妙な方法で実装しています。Visual C++* で wsprintf 関数を使用する場合、ワイド文字の文字列の出力には “%s” を使用し、char* 文字列の出力には “%S” を使用すべきです。単に奇妙なケースと言えるでしょう。これは、クロスプラットフォーム・アプリケーションを開発する場合に陥りやすい罠です。
正しいコード
ここで紹介するコードは、美しいものではありませんが、修正すべき点を示しています。
char *p = NULL; ... #ifdef defined(_WIN32) wprintf(L"Using power link directory: %S\n"), p); #else wprintf(L"Using power link directory: %s\n"), p); #endif
推奨事項
ここでは、特に推奨事項はありません。wprintf() のような関数を使用する場合に起こりうる意外な問題について、皆さんに警告したかっただけです。
Visual Studio* 2015 以降では、可搬性の高いコードを記述するための推奨があります。ISO C (C99) との互換性のため、プリプロセッサーに _CRT_STDIO_ISO_WIDE_SPECIFIERS マクロについて知らせるべきです。
この場合、次のコードは正しいです。
const wchar_t *p = L"abcdef"; const char *x = "xyz"; wprintf(L"%S %s", p, x);
アナライザーは、_CRT_STDIO_ISO_WIDE_SPECIFIERS を認識し、解析時に考慮します。
ISO C との互換性を有効にする (_CRT_STDIO_ISO_WIDE_SPECIFIERS マクロが宣言されている) 場合、”%Ts” 形式の指示子を使用して以前の動作にすることができます。
一般に、ワイド文字シンボルはかなり複雑で、短い記事ではカバーできません。このトピックに関してさらに詳しく調査するには、次の資料が参考になります。
- バグ 1121290 – printf 関数ファミリーで s と ls を区別する (英語)
- swprintf での MBCS から Unicode への変換 (英語)
- Visual Studio* の swprintf がすべての %s フォーマッターで char * の代わりに wchar_t * を要求 (英語)
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。