この記事は、インテル® デベロッパー・ゾーンに公開されている「The Ultimate Question of Programming, Refactoring, and Everything」の日本語参考訳です。
18. ある言語の知識は必ずしも別の言語に適用できるとは限らない
Putty プロジェクトから抜粋した以下のコードについて考えてみます。非効率的なコードは、次の PVS-Studio 診断によって検出されます。
V814 Decreased performance. Calls to the ‘strlen’ function have being made multiple times when a condition for the loop’s continuation was calculated. (V814 パフォーマンス低下。ループの継続条件の計算時に ‘strlen’ 関数が複数回呼び出されました。)
static void tell_str(FILE * stream, char *str) { unsigned int i; for (i = 0; i < strlen(str); ++i) tell_char(stream, str[i]); }
説明
これは実際にはエラーではありませんが、strlen() 関数がループの各反復で呼び出されているため、長い文字列を扱う場合には非常に非効率的です。
通常、このようなエラーは、Pascal (または Delphi) での開発経験があるプログラマーが記述したコードで見られます。Pascal では、ループの終了条件の評価が一度しか計算されないため、このようなコードが非常に一般的です。
Pascal コードの例を見てみましょう。pstrlen() が一度だけ呼び出されているため、called は一度だけ出力されます。
program test; var i : integer; str : string; function pstrlen(str : string): integer; begin writeln('called'); pstrlen := Length(str); end; begin str := 'a pascal string'; for i:= 1 to pstrlen(str) do writeln(str[i]); end.
効率良いコード
static void tell_str(FILE * stream, char *str) { size_t i; const size_t len = strlen(str); for (i = 0; i < len; ++i) tell_char(stream, str[i]); }
推奨事項
C/C++ では、ループの終了条件が各反復で再計算されます。そのため、非効率な遅い関数の呼び出しを終了判定に含めることは、特にループに入る前にそれを一度計算すれば済む場合、良いアイデアとは言えません。
一部のケースでは、コンパイラーはこのような strlen() を含むコードを最適化できる可能性があります。例えば、ポインターが常に同じ文字列リテラルを参照する場合などです。しかし、それを期待すべきではありません。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。