プログラミング、リファクタリング、そしてすべてにおける究極の疑問: No. 30

その他インテル® DPC++/C++ コンパイラー特集

この記事は、インテル® デベロッパー・ゾーンに公開されている「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” 形式の指示子を使用して以前の動作にすることができます。

一般に、ワイド文字シンボルはかなり複雑で、短い記事ではカバーできません。このトピックに関してさらに詳しく調査するには、次の資料が参考になります。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

タイトルとURLをコピーしました