私は現在、文字列の長さを頻繁に比較する必要があるCプログラムを作成しているので、次のヘルパー関数を作成しました。
int strlonger(char *s1, char *s2) {
return strlen(s1) - strlen(s2) > 0;
}
s1
長さが。より短い場合でも、関数がtrueを返すことに気づきました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 < 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を参照)。
モジュラー演算を実行するときに使用してください。
セットを表すためにビットを使用する場合は使用してください。これは、符号拡張なしで論理的な右シフトを実行できるため、多くの場合便利です。
もちろん、これらの「ルール」に反することを決定する状況があるかもしれません。ただし、ほとんどの場合、これらの提案に従うと、コードの操作が簡単になり、エラーが発生しにくくなります。
strlen
size_t
タイプのaをtypedef
返しますunsigned
。
それで、
(unsigned) 4 - (unsigned) 7 == (unsigned) - 3
すべてのunsigned
値は。以上です0
。strlen
によって返される変数をに変換してみてくださいlong int
。
Alex Lockwood の答えが最良の解決策です (コンパクトで明確なセマンティクスなど)。
size_t
:ptrdiff_t
の符号付き形式に明示的に変換することが理にかなっている場合もあります。
return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;
これを行う場合、値が a (仮数ビットが 1 つ少ない) にsize_t
収まることを確認する必要があります。ptrdiff_t