9

1 の補数アーキテクチャで次のコードを検討してください。

int zero = 0;
int negzero = -0;
std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;
std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;
  • コードはどのような出力を生成しますか?
  • 標準で定義されている行、実装に依存する行、および未定義の動作である行はどれですか?
4

3 に答える 3

3

標準の私の解釈に基づいて:

§3.9.1/p3 Fundamental types [basic.fundamental]の C++ 標準は、実際に C 標準でボールを投げます。

符号付きおよび符号なしの整数型は、C 標準のセクション 5.2.4.2.1 で指定された制約を満たさなければなりません。

ここで、ISO/IEC 9899:2011 セクション 5.2.4.2.1 に移動すると、§6.2.6.2/p2 整数型( Emphasis Mine )への前方参照として提供されます。

符号ビットがゼロの場合、結果の値には影響しません。符号ビットが 1 の場合、値は次のいずれかの方法で変更されます。

  • 符号ビット 0 の対応する値は否定されます (符号と大きさ)。

  • 符号ビットの値は -(2^M) (2 の補数) です。

  • 符号ビットの値は -(2^M − 1) (1 の補数) です。

これらのうちどれが適用されるかは実装定義であり、符号ビットが 1 ですべての値ビットが 0 (最初の 2 つの場合) の値、または符号ビットとすべての値ビットが 1 (1 の補数の場合) の値がトラップ表現であるかどうかと同様です。または正常値。符号と大きさと 1 の補数の場合、この表現が正常な値であれば、負のゼロと呼ばれます。

したがって、負のゼロの存在は実装定義です。

パラグラフ 3 をさらに進めると、次のようになります。

実装が負のゼロをサポートする場合、それらは以下によってのみ生成されます:

  • そのような値を生成するオペランドを持つ &、|、^、~、<<、および >> 演算子。

  • +、-、*、/、および % 演算子で、1 つのオペランドが負のゼロであり、結果がゼロです。

  • 上記のケースに基づく複合代入演算子。

これらのケースが実際に負のゼロまたは通常のゼロを生成するかどうか、およびオブジェクトに格納されたときに負のゼロが通常のゼロになるかどうかは指定されていません。

したがって、表示した関連ケースが負のゼロを生成するかどうかは不明です。

パラグラフ 4 に進みます。

実装が負のゼロをサポートしていない場合、そのような値を生成するオペランドを持つ &、|、^、~、<<、および >> 演算子の動作は未定義です。

したがって、関連する操作が未定義の動作をもたらすかどうかは、実装が負のゼロをサポートしているかどうかによって異なります。

于 2015-12-08T08:18:29.260 に答える
3

まず、最初の前提が間違っています。

int negzero = -0;

適合するアーキテクチャでは通常のゼロを生成する必要があります。

そのための参照は、@ 101010 の回答に記載されています。

3.9.1 基本型 [basic.fundamental] §3:

... 符号付きおよび符号なしの整数型は、C 標準のセクション 5.2.4.2.1 で指定された制約を満たさなければなりません。

C リファレンスの後半: 5.2.4.2.1 整数型のサイズ

... 前方参照: 型の表現 (6.2.6)

および (まだ C): 6.2.6 型の表現 / 6.2.6.2 整数型 § 3

実装が負のゼロをサポートする場合、それらは以下によってのみ生成されます:

  • そのような値を生成する引数を持つ &、|、^、~、<<、および >> 演算子。

  • +、-、*、/、および % の各演算子で、引数の 1 つが負のゼロであり、結果がゼロになります。

  • 上記のケースに基づく複合代入演算子。

そのような構造でnegzero = -0なく、負の 0 を生成してはなりません。

次の行では、負の 0 は、それをサポートする実装でビットごとに生成されたと仮定します。

C++ 標準では、負のゼロについてはまったく言及されていません。C 標準では、それらの存在は実装に依存しているとだけ述べています。負のゼロが関係演算子または等価演算子の通常のゼロと等しいかどうかを明示的に述べている段落を見つけることができませんでした。

したがって、C リファレンスで引用します: 6.5.8 関係演算子 §6

< (より小さい)、> (より大きい)、<= (より小さいか等しい)、および >= (より大きいか等しい) の各演算子は、指定された関係が真の場合は 1 を返し、そうでない場合は 0 を返します。 false.92) 結果の型は int です。

C++ 5.9 関係演算子 [expr.rel] §5

両方のオペランド (変換後) が算術型または列挙型の場合、各演算子は、指定された関係が true の場合は true を返し、false の場合は false を返します。

標準の私の解釈では、実装によって整数値 0 (負のゼロ) の代替表現が許可される場合がありますが、それでも値 0 の表現であり、C 6.2.6.2 整数型 § 3 言います:

負のゼロ [...] は、1 つの引数が負のゼロで結果がゼロである[...] +、-、*、/、および % 演算子によってのみ生成されます

つまり、結果が 0 でない場合、負の 0 は通常の 0 として機能する必要があります。

したがって、少なくともこれら 2 行は完全に定義されており、次のようになり1ます。

std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

この行は、実装に依存するものとして明確に定義されています。

std::cout<<(~negzero)<<(~zero)<<std::endl;

実装にはパディング ビットがある可能性があるためです。パディング ビットがない場合、1 の補数アーキテクチャ~zero negzeroであるため、~negzeroを生成する必要0がありますが、負のゼロを として表示する0か、 として表示するかを標準で見つけることができませんでした-0。負の浮動小数点0 はマイナス記号を付けて表示する必要がありますが、整数の負の値について明示的なものはないようです。

関係演算子と等価演算子を含む最後の 3 行については、標準で明示的なものは何もないため、実装定義であると言えます。

TL/DR:

実装依存:

std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;

完全に定義されており、1 を生成する必要があります。

std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;
于 2015-12-08T10:18:14.293 に答える
-2

まず第一に、1 の補数アーキテクチャ (または負のゼロを区別することさえ) はかなりまれであり、それには理由があります。基本的に、(ハードウェア的に) 1 の補数よりも 2 の補数を加算する方が簡単です。

あなたが投稿したコードには、未定義の動作や実装定義の動作さえないようです。おそらく負のゼロになることはありません(または、通常のゼロと区別されるべきではありません)。

負のゼロを生成するのはそれほど簡単ではありません (それができたとしても、せいぜい実装で定義された動作です)。1 の補数のアーキテクチャの場合、.~0ではなく (ビットごとの反転)によって生成され-0ます。

C++ 標準は、基本型の動作に関する実際の表現と要件についてかなりあいまいです (つまり、仕様は数値の実際の意味のみを扱っていることを意味します)。これが意味することは、基本的に、数値の内部表現と実際の値を関連付けるのがうまくいかないということです。したがって、これを正しく行って使用したとしても~0(または実装に適した方法で)、負のゼロの値がまだゼロであるため、標準はまだ表現を気にしていないようです。

#define zero (0)
#define negzero (~0)
std::cout<<(negzero < zero)<<std::endl;
std::cout<<(negzero <= zero)<<std::endl;
std::cout<<(negzero == zero)<<std::endl;
std::cout<<(~negzero)<<(~zero)<<std::endl;
std::cout<<(1 << negzero)<<std::endl;
std::cout<<(1 >> negzero)<<std::endl;

negzero最初の 3 行は、 が と同じように定義されているかのように、同じ出力を生成するはずzeroです。3 行目は 2 つのゼロを出力する必要があります (標準では符号なし0でレンダリングする必要があるため)。0最後の 2 つは 1 を出力するはずです。

実際に負のゼロについて言及しているC標準にあるいくつかのヒント(負のゼロを生成する方法について)がありますが、通常のゼロよりも小さい値を比較する必要があるという言及はないと思います。C 標準では、負のゼロはオブジェクトのストレージに耐えられない可能性があることが示唆されています (そのため、上記の例ではそれを避けました)。

C と C++ の関係からすると、負のゼロは C++ でも C と同じように生成されると考えるのが妥当であり、標準ではそれが許可されているようです。C++ 標準では (未定義の動作を介して) 他の方法が許可されていますが、定義済みの動作を介して利用できる方法は他にないようです。したがって、C++ 実装が合理的な方法で負のゼロを生成できる場合、同様の C 実装の場合と同じになることはかなり確実です。

于 2015-12-08T07:26:04.223 に答える