20

署名された/署名されていない比較に関するこの質問を読んだ後(彼らは私が言うだろう数日ごとに出てきます):

なぜ適切な署名された署名されていない比較がなく、代わりにこの恐ろしい混乱があるのだろうか?この小さなプログラムから出力を取得します。

#include <stdio.h>
#define C(T1,T2)\
 {signed   T1 a=-1;\
 unsigned T2 b=1;\
  printf("(signed %5s)%d < (unsigned %5s)%d = %d\n",#T1,(int)a,#T2,(int)b,(a<b));}\

 #define C1(T) printf("%s:%d\n",#T,(int)sizeof(T)); C(T,char);C(T,short);C(T,int);C(T,long);
int main()
{
 C1(char); C1(short); C1(int); C1(long); 
}

私の標準コンパイラ(gcc、64ビット)でコンパイルすると、次のようになります。

char:1
(signed  char)-1 < (unsigned  char)1 = 1
(signed  char)-1 < (unsigned short)1 = 1
(signed  char)-1 < (unsigned   int)1 = 0
(signed  char)-1 < (unsigned  long)1 = 0
short:2
(signed short)-1 < (unsigned  char)1 = 1
(signed short)-1 < (unsigned short)1 = 1
(signed short)-1 < (unsigned   int)1 = 0
(signed short)-1 < (unsigned  long)1 = 0
int:4
(signed   int)-1 < (unsigned  char)1 = 1
(signed   int)-1 < (unsigned short)1 = 1
(signed   int)-1 < (unsigned   int)1 = 0
(signed   int)-1 < (unsigned  long)1 = 0
long:8
(signed  long)-1 < (unsigned  char)1 = 1
(signed  long)-1 < (unsigned short)1 = 1
(signed  long)-1 < (unsigned   int)1 = 1
(signed  long)-1 < (unsigned  long)1 = 0

32ビット用にコンパイルした場合、結果は次の点を除いて同じです。

long:4
(signed  long)-1 < (unsigned   int)1 = 0

「どうやって?」これらすべての中で簡単に見つけることができます。C99標準のセクション6.3またはC++の第4章に移動し、オペランドが共通型に変換される方法を説明する句を掘り下げてください。共通型が負の値を再解釈すると、これが機能しなくなる可能性があります。

しかし、「なぜ?」はどうですか。ご覧のとおり、「<」はすべてのケースの50%で失敗します。また、タイプの具体的なサイズに依存するため、プラットフォームに依存します。考慮すべき点は次のとおりです。

  • 変換と比較のプロセスは、驚き最小の原則の代表的な例ではありません。

  • 私は、テロリストによって書かれて(short)-1 > (unsigned)1ないという命題に依存しているコードがそこにあるとは思わない。

  • テンプレートコードを使用してC++を使用している場合、これはすべてひどいものです。正しい「<」を編成するには、型特性の魔法が必要だからです。


結局のところ、さまざまなタイプの符号付き値と符号なし値を比較するの簡単です。

signed X < unsigned Y -> (a<(X)0) || ((Z)a<(Z)b) where Z=X|Y 

事前チェックは安価であり、a> = 0が静的に証明できる場合は、コンパイラーによって最適化することもできます。

だからここに私の質問があります:

C / C ++と比較して安全な符号付き/符号なしの比較を追加すると、言語または既存のコードが壊れますか?

(「言語が壊れますか」とは、この変更に対応するために、言語のさまざまな部分に大規模な変更を加える必要があることを意味します)


更新: これを古き良きTurbo-C ++ 3.0で実行し、次の出力を取得しました:

char:1
(signed  char)-1 < (unsigned  char)1 = 0

なぜ(signed char)-1 < (unsigned char) == 0ここにあるのですか?

4

6 に答える 6

12

私の答えはCのみです。

Cには、すべての可能な整数型のすべての可能な値に対応できる型はありません。これに最も近いC99はintmax_tanduintmax_tであり、それらの交点はそれぞれの範囲の半分しかカバーしていません。

したがって、最初に共通型にx <= y変換xしてから単純な操作を行うなど、数学的な値の比較を実装することはできません。yこれは、オペレーターの作業方法の一般原則からの大きな逸脱です。また、一般的なハードウェアでは単一の命令になりがちなものにオペレーターが対応しているという直感も破られます。

この追加の複雑さを言語に追加したとしても(そして実装作成者に余分な負担をかけたとしても)、それはあまり良い特性を持っていません。たとえば、x <= yはまだと同等ではありませんx - y <= 0。これらすべての優れたプロパティが必要な場合は、任意のサイズの整数を言語の一部にする必要があります。

そこにはたくさんの古いUNIXコードがあり、おそらくあなたのマシンで実行されているものもあると思い(int)-1 > (unsigned)1ます。(わかりました、多分それは自由の闘士によって書かれました;-)

lisp / haskell / python / $ favorite_language_with_bignums_built_inが必要な場合は、どこにあるか知っています...

于 2010-08-13T12:28:37.170 に答える
8

はい、それは言語/既存のコードを壊します。お気づきのように、この言語では、符号付きオペランドと符号なしオペランドを一緒に使用した場合の動作を慎重に指定しています。比較演算子を使用したこの動作は、次のようないくつかの重要なイディオムに不可欠です。

if (x-'0' < 10U)

(同等性の比較)のようなものは言うまでもありません:

size_t l = mbrtowc(&wc, s, n, &state);
if (l==-1) ... /* Note that mbrtowc returns (size_t)-1 on failure */

余談ですが、符号付き/符号なしの混合比較に「自然な」動作を指定すると、入力の制約のためにすでに「自然な」動作をしている安全な方法でそのような比較を現在使用しているプログラムでも、パフォーマンスが大幅に低下します。これは、コンパイラーが判別するのに苦労するでしょう(またはまったく判別できない可能性があります)。これらのテストを処理するための独自のコードを作成する際に、パフォーマンスの低下がどのようになるかをすでに確認していると思いますが、それはきれいではありません。

于 2010-08-13T12:21:36.640 に答える
7

言語が壊れるとは思いませんが、はい、既存のコードが壊れている可能性があります(そして、破損はコンパイラレベルで検出するのがおそらく難しいでしょう)。

あなたと私が一緒に想像できるよりもはるかに多くのコードがCとC++で書かれています(その一部はテロリストによって書かれているかもしれません)。

「提案」に依存する(short)-1 > (unsigned)1ことは、誰かによって意図せずに行われる可能性があります。複雑なビット操作などを扱うCコードがたくさんあります。一部のプログラマーがそのようなコードで現在の比較動作を使用している可能性は十分にあります。(他の人々はすでにそのようなコードの素晴らしい例を提供しており、コードは私が予想するよりもさらに単純です)。

現在の解決策は、代わりにそのような比較について警告し、解決策をプログラマーに任せることです。これは、CとC++がどのように機能するかという精神に基づいていると思います。また、コンパイラレベルでそれを解決すると、パフォーマンスが低下します。これは、CおよびC++プログラマーが非常に敏感なことです。1つではなく2つのテストは小さな問題のように思えるかもしれませんが、これが問題になるCコードはおそらくたくさんあります。たとえば、一般的なデータ型への明示的なキャストを使用して以前の動作を強制することで解決できますが、これもプログラマーの注意が必要になるため、単純な警告に勝るものはありません。

于 2010-08-13T12:11:12.597 に答える
1

C++はローマ帝国のようなものだと思います。その大きく、そしてそれを破壊しようとしているものを修正するにはあまりにも確立されています。

c ++ 0x(およびboost)は、恐ろしい恐ろしい構文の例です。親だけが愛することができる種類の赤ちゃんであり、10年前のシンプルでエレガントな(ただし厳しく制限された)c++からはほど遠いものです。

重要なのは、整数型の比較のように非常に単純なものを「修正」するまでに、十分なレガシーおよび既存のc ++コードが壊れているため、単に新しい言語と呼んでもよいということです。

そして、一度壊れると、遡及修正の対象となるものが他にもたくさんあります。

于 2010-08-13T13:01:16.150 に答える
0

異なるC言語型のオペランドを組み合わせて使用​​する場合に、実行時に驚き最小の原則を支持することに近いルールを言語が定義する唯一の方法は、少なくとも一部のコンテキストで暗黙的な型変換をコンパイラーに禁止させることです。 (「サプライズ」を「なぜこれがコンパイルされないのですか?」にシフトし、将来予期しないバグが発生する可能性を低くします)、ストレージ形式ごとに複数の型を定義します(たとえば、各整数型のラッピングと非ラッピングの両方のバリアント) )、 または両方。

符号付きと符号なしの16ビット整数のラッピングバージョンと非ラッピングバージョンの両方など、ストレージ形式ごとに複数のタイプがあると、コンパイラは「効率が上がる場合に備えて、ここでは16ビット値を使用しています。 、ただし、0〜65535の範囲を超えることはなく、何が起こったとしても気になりません)」および「65535にラップする必要がある16ビット値を使用しているため、負になります」。後者の場合、そのような値に32ビットレジスタを使用するコンパイラは、各算術演算の後にそれをマスクする必要がありますが、前者の場合、コンパイラはそれを省略できます。あなたの特定の願いに関して、長い署名された非ラッピングと非ラッピングの比較の意味unsigned longは明らかであり、それを実現するために必要な複数命令シーケンスをコンパイラーが生成することが適切です(負の数の非ラッピングへの変換はunsigned long未定義動作であるため、コンパイラーに次の動作を定義させるこれらのタイプの比較演算子は、指定されている可能性のある他のものと競合しません)。

残念ながら、コンパイラにオペランドの混合比較の警告を生成させる以外に、上記のように新しい型を追加せずにC言語が存在するため、C言語で実行できることはあまりありません。このような新しいタイプの追加は改善と見なしますが、私は息を止めません。

于 2013-09-05T20:53:51.263 に答える
0

整数型間の比較で実際の数学値を比較する場合、整数と浮動小数点の比較でも同じことが起こりたいと思います。また、任意の64ビット整数と任意の倍精度浮動小数点数の正確な値を比較することは非常に困難です。しかし、コンパイラーはおそらく私よりも優れているでしょう。

于 2014-06-30T09:27:29.573 に答える