web-dev-qa-db-ja.com

「-1 >> 5;」 Cの不特定の動作?

C11§6.5.7パラグラフ5:

E1 >> E2の結果は、E1右シフトE2ビット位置です。 E1に符号なしの型がある場合、またはE1に符号付きの型と非負の値がある場合、結果の値はE1 / 2*^E2の商の整数部になります。 E1が符号付きタイプで負の値を持っている場合、結果の値は実装定義です。

しかし、 viva64 リファレンスドキュメントは次のように述べています。

int B;
B = -1 >> 5; // unspecified behavior

このコードを [〜#〜] gcc [〜#〜] で実行しましたが、常に出力-1が返されます。

したがって、標準的な発言は「E1が符号付きのタイプと負の値を持っている場合、結果の値は実装定義です」、しかしその文書によると-1>>5;不特定の動作です。

だから、-1>>5;はCで指定されていない動作ですか?どちらが正しい?

38
msc

両方とも正しいです。実装定義の動作は、特定されていない特定の動作です。

「実装定義の動作」を定義する C標準 のセクション3.4.1を引用:

1実装定義の動作

各実装が選択方法を文書化する不特定の動作

2例実装定義の動作の例は、符号付き整数が右にシフトされたときの高位ビットの伝播です。

「不特定の動作」を定義するセクション3.4.4から:

1不特定の動作

指定されていない値の使用、またはこの国際規格が2つ以上の可能性を提供し、いずれの場合でも選択される追加の要件を課さない他の動作

2例指定されていない動作の例は、関数の引数が評価される順序です。

GCCについては、操作は実装定義であるため、常に同じ答えが得られます。符号拡張により負数の右シフトを実装します

GCCドキュメント から:

符号付き整数(C90 6.3、C99およびC11 6.5)でのビット演算の結果。

ビットごとの演算子は、符号ビットと値ビットの両方を含む値の表現に作用します。符号ビットは、最高値のビットのすぐ上と見なされます。 署名済み>>は、符号拡張によって負の数に作用します。

C言語の拡張機能として、GCCは、C99およびC11で指定された緯度を、署名された<<未定義として。しかしながら、 -fsanitize=shift(および-fsanitize=undefined)そのような場合を診断します。定数式が必要な場合にも診断されます。

39
dbush

「不特定の動作」と「実装の定義」は矛盾していません。これは単に、C標準が何を行う必要があるかを指定しておらず、さまざまな実装が「正しい」とみなすことを実行できることを意味します。

1つのコンパイラで複数回実行し、同じ結果を取得することは、その特定のコンパイラが一貫していることを意味します。異なるコンパイラでは異なる結果が得られる場合があります。

14
skrrgwasme

実装定義の動作は、指定されていない動作のサブクラス、つまり、規格で指定されていない動作です。

C89の欠陥レポート#154は、委員会に 実装定義の動作 ;の制限を尋ねました。委員会は、実装は必要な動作を定義でき、一定である必要はないと答えます。

実装が行う必要があるのは、howを文書化することです。これは、準拠する実装がわざわざhow選択が行われます。おそらく、これらの実装の大半では、テキストが「ランダム」または「コンパイラ最適化レベルに応じて」と言うからです。 「または「ローカル変数のレジスタ割り当てに応じて」。

2
Antti Haapala

現在の回答が得られません。 C標準では、負の数を右にシフトすることは実装定義の動作であると明確に述べています。これは、not不特定の動作であり、他の何かを意味します。正しく引用すると(C17 6.5.7§5):

E1 >> E2の結果は、E1を右シフトしたE2ビット位置です。 /-/
E1に符号付きタイプと負の値がある場合、結果の値は実装定義です。

これは、コンパイラーmustがその動作を文書化することを意味します。限目。

実際には、ドキュメントは、コンパイラが算術右シフトを使用するか論理右シフトを使用するかを通知する必要があります。


これは、文書化する必要のない実装固有の動作である不特定の動作とは対照的です。不特定の動作は、次の2つの場合に使用されます。

  • コンパイラの動作が実装の秘密である可能性がある場合、コンパイラベンダーが競合他社に公開することを強制しないでください。
  • OSやRAMメモリセルなどの基礎となる詳細がどのように機能するかを文書化するためにコンパイラを煩わせることができない場合。

たとえば、コンパイラは次のようなコードで評価の順序を文書化する必要はありません。

a  = f1() + f2();
a += f1() + f2();

部分式が評価される順序を文書化すると、コンパイラーの内部式ツリーとオプティマイザーの動作に関する詳細が明らかになり、コンパイラーが競合よりも優れたコードを生成したりコンパイルが高速になる理由が明らかになります。これは、C標準が最初に作成されたとき、大きなことでした。最近では、優れたオープンソースコンパイラがいくつかあるので、それはもはや秘密ではありません。

同様に、コンパイラはこのコードが出力する内容を文書化する必要はありません。

int a;
int ptr = &a;
printf("%d", *ptr);

aは不定の値であり、出力は指定されていません-実際には、出力はその特定のRAMセルに保存されたものに依存します。 (「UB」と叫ぶ前に、 (なぜ)は初期化されていない変数undefined behaviorを使用しているのですか? を参照してください)。

2
Lundin