私は現在、文字列の長さを頻繁に比較する必要があるCプログラムを作成しているので、次のヘルパー関数を作成しました。
int strlonger(char *s1, char *s2) {
return strlen(s1) - strlen(s2) > 0;
}
s1
の長さがs2
より短い場合でも関数がtrueを返すことに気づきました。誰かがこの奇妙な行動を説明できますか?
あなたが遭遇したのは、符号付きと符号なしの両方の量を含む式を処理するときに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 < 0U
_
2番目のオペランドは符号なしであるため、最初のオペランドは暗黙的にunsignedにキャストされます。したがって、式は比較と同等です。
_4294967295U < 0U
_
もちろんこれは誤りです。これはおそらくあなたが期待していた振る舞いではありません。
配列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
の無効な要素にアクセスしようとします。
コードは、length
をint
として宣言するか、for
ループのテストを_i < length
_に変更することで修正できます。
ここではあまり物議を醸すようなことは言いたくありませんが、Cでプログラムを書くときによく守るルールをいくつか紹介します。
しないでください数値が負でないという理由だけで使用してください。間違いを犯しやすい、そして、これらの間違いは時々信じられないほど微妙です(例2に示されているように)。
[〜#〜] do [〜#〜]モジュラー演算を実行するときに使用します。
[〜#〜] do [〜#〜]ビットを使用してセットを表す場合に使用します。 =これは、符号拡張なしで論理的な右シフトを実行できるため、多くの場合便利です。
もちろん、これらの「ルール」に反することを決定する状況があるかもしれません。ただし、ほとんどの場合、これらの提案に従うと、コードの操作が簡単になり、エラーが発生しにくくなります。
strlen
は、typedef
タイプのunsigned
であるsize_t
を返します。
そう、
(unsigned) 4 - (unsigned) 7 == (unsigned) - 3
すべてのunsigned
値は0
以上です。 strlen
から返された変数をlong int
に変換してみてください。
Alex Lockwoodの answer が最良の解決策です(コンパクトで明確なセマンティクスなど)。
署名された形式のsize_t
:ptrdiff_t
に明示的に変換することが理にかなっている場合があります。
return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;
これを行う場合は、size_t
値がptrdiff_t
(仮数ビットが1つ少ない)に収まるようにする必要があります。