この記事は、インテル® デベロッパー・ゾーンに公開されている「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() を含むコードを最適化できる可能性があります。例えば、ポインターが常に同じ文字列リテラルを参照する場合などです。しかし、それを期待すべきではありません。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください。

