1. ホーム
  2. c

「strlen(s1) - strlen(s2)」が0より小さいことはありません。

2023-09-06 10:43:39

質問

現在、文字列の長さを頻繁に比較する必要のあるCプログラムを書いているので、以下のヘルパー関数を書きました。

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

この関数が真を返すことに気づいたのは s1 よりも短い s2 . どなたかこの奇妙な動作を説明してください。

どのように解決するのですか?

あなたが遭遇したのは、符号付きと符号なしの両方の量を含む式を処理するときに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 つの式に符号なしと符号ありの量が混在している場合、符号ありの値は暗黙のうちに符号なしにキャストされます。 .

例1:

次のような式を考えてみましょう。

-1 < 0U

第2オペランドは符号なしなので、第1オペランドは を暗黙のうちにキャストします。 を符号なしにキャストし、したがってこの式は比較と等価になります。

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 .

結論 Unsignedはいつ使うべきか?

ここであまり議論を呼ぶようなことは言いたくないのですが、私がCでプログラムを書くときによく守っているルールをいくつか紹介します。

  • 禁止事項 は、数字が非負であるという理由だけで使用することはできません。 間違えやすく、しかもそれが非常に微妙な場合もある(例2)。

  • DO は、モジュール演算を行う際に使用します。

  • DO ビットを使って集合を表現する場合に使用します。 符号拡張をせずに論理右シフトができるので便利なことが多い。

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