web-dev-qa-db-ja.com

「strlen(s1)-strlen(s2)」がゼロ以上になることはありません

私は現在、文字列の長さを頻繁に比較する必要があるCプログラムを作成しているので、次のヘルパー関数を作成しました。

int strlonger(char *s1, char *s2) {
    return strlen(s1) - strlen(s2) > 0;
}

s1の長さがs2より短い場合でも関数がtrueを返すことに気づきました。誰かがこの奇妙な行動を説明できますか?

76
Adrian Monk

あなたが遭遇したのは、符号付きと符号なしの両方の量を含む式を処理するときにCで発生するいくつかの特有の動作です。

一方のオペランドが符号付きでもう一方が符号なしの演算を実行すると、Cは暗黙的に符号付き引数を符号なしに変換し、数値が負でないことを前提として演算を実行します。この規則により、_<_や_>_などの関係演算子の動作が直感的でないことがよくあります。

ヘルパー関数に関しては、strlenはタイプ_size_t_(符号なしの量)を返すため、差と比較は両方とも符号なしの算術を使用して計算されることに注意してください。 _s1_が_s2_よりも短い場合、差strlen(s1) - strlen(s2)は負になるはずですが、代わりに_0_よりも大きい符号なしの大きな数値になります。したがって、

_return strlen(s1) - strlen(s2) > 0;
_

_1_が_s1_より短い場合でも、_s2_を返します。関数を修正するには、代わりに次のコードを使用してください。

_return strlen(s1) > strlen(s2);
_

Cの素晴らしい世界へようこそ! :)


その他の例

この質問は最近多くの注目を集めているので、アイデアを確実に伝えるために、いくつかの(簡単な)例を示したいと思います。 2の補数表現を使用する32ビットマシンで作業していると仮定します。

Cで符号なし/符号付き変数を操作するときに理解する重要な概念は、1つの式に符号なしと符号付きの量が混在している場合、符号付きの値は暗黙的にunsigned

例1:

次の式について考えてみます。

_-1 < 0U
_

2番目のオペランドは符号なしであるため、最初のオペランドは暗黙的にunsignedにキャストされます。したがって、式は比較と同等です。

_4294967295U < 0U
_

もちろんこれは誤りです。これはおそらくあなたが期待していた振る舞いではありません。

例2:

配列aの要素を合計しようとする次のコードについて考えてみます。ここで、要素の数はパラメーターlengthで指定されます。

_int sum_array_elements(int a[], unsigned length) {
    int i;
    int result = 0;

    for (i = 0; i <= length-1; i++) 
        result += a[i];

    return result;
}
_

この関数は、符号付きから符号なしへの暗黙のキャストが原因でバグが発生しやすいことを示すように設計されています。パラメータlengthを符号なしとして渡すのは非常に自然なようです。結局のところ、誰が負の長さを使用したいと思うでしょうか?停止基準_i <= length-1_も非常に直感的に思えます。ただし、引数lengthが_0_と等しい場合、これら2つの組み合わせは予期しない結果をもたらします。

パラメータlengthは符号なしであるため、計算_0-1_は符号なし算術を使用して実行されます。これは、モジュラー加算と同等です。結果はUMaxになります。 _<=_比較も符号なし比較を使用して実行され、任意の数がUMax以下であるため、比較は常に保持されます。したがって、コードは配列aの無効な要素にアクセスしようとします。

コードは、lengthintとして宣言するか、forループのテストを_i < length_に変更することで修正できます。

結論:いつ署名なしを使用する必要がありますか?

ここではあまり物議を醸すようなことは言いたくありませんが、Cでプログラムを書くときによく守るルールをいくつか紹介します。

  • しないでください数値が負でないという理由だけで使用してください。間違いを犯しやすい、そして、これらの間違いは時々信じられないほど微妙です(例2に示されているように)。

  • [〜#〜] do [〜#〜]モジュラー演算を実行するときに使用します。

  • [〜#〜] do [〜#〜]ビットを使用してセットを表す場合に使用します。 =これは、符号拡張なしで論理的な右シフトを実行できるため、多くの場合便利です。

もちろん、これらの「ルール」に反することを決定する状況があるかもしれません。ただし、ほとんどの場合、これらの提案に従うと、コードの操作が簡単になり、エラーが発生しにくくなります。

174
Alex Lockwood

strlenは、typedefタイプのunsignedであるsize_tを返します。

そう、

(unsigned) 4 - (unsigned) 7 == (unsigned) - 3

すべてのunsigned値は0以上です。 strlenから返された変数をlong intに変換してみてください。

25
pmg

Alex Lockwoodの answer が最良の解決策です(コンパクトで明確なセマンティクスなど)。

署名された形式のsize_tptrdiff_tに明示的に変換することが理にかなっている場合があります。

return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;

これを行う場合は、size_t値がptrdiff_t(仮数ビットが1つ少ない)に収まるようにする必要があります。

1
Mr Fooz