これら 2 つのマクロの違いは何ですか?
#define swap(a, b) (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b)))
または
#define swap(a, b) (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))
ここで 2 番目のマクロを見ましたが、なぜ最初のマクロのように書かれていないのか理解できませんでしたか? 私が逃した特別な理由はありますか?
これら 2 つのマクロの違いは何ですか?
#define swap(a, b) (((a) ^ (b)) && ((a) ^= (b) ^= (a) ^= (b)))
または
#define swap(a, b) (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))
ここで 2 番目のマクロを見ましたが、なぜ最初のマクロのように書かれていないのか理解できませんでしたか? 私が逃した特別な理由はありますか?
First は、C99 と C11 の両方で未定義の動作を呼び出します。
C99 では、次のように理解できます。シーケンス ポイントがないため、未定義の動作が発生します。
前のシーケンス ポイントと次のシーケンス ポイントの間で、オブジェクトの格納値は、式の評価によって最大 1 回変更されます。さらに、保存する値を決定するためにのみ、前の値にアクセスする必要があります。
説明:
最初のものはa2 つのシーケンス ポイント間で 2 回変更されているため、次のステートメントに従って動作が定義されていません:前のシーケンス ポイントと次のシーケンス ポイントの間で、オブジェクトは格納された値を式の評価によって最大 1 回変更する必要があります。それだけです(考える必要はありませんb)。
C11のドキュメントには次のように書かれています:
スカラー オブジェクトに対する副作用が、同じスカラー オブジェクトに対する別の副作用、または同じスカラー オブジェクトの値を使用した値の計算に対して順序付けされていない場合、動作は undefinedです。式の部分式に複数の順序付けが許可されている場合、いずれかの順序付けでそのような順序付けされていない副作用が発生した場合の動作は未定義です。84)
では(a) ^= (b) ^= (a) ^= (b)、 on の副作用aはシーケンス化されていないため、未定義の動作を引き起こします。C11 6.5 p1 には次のように記載されていることに注意してください。
[...] 演算子のオペランドの値の計算は、演算子の結果の値の計算の前に並べられます。
これにより、
(a) ^= (b) ^= (a) ^= (b)
| | | |
1 2 3 4
すべての部分式 1、2、3、および 4 は、左端の^=演算子の結果計算の前に計算されることが保証されています。ただし、これは式 3 の副作用が左端の^=演算子の結果の値計算の前に保証されることを保証するものではありません。
1.強調は私のものです。
最初のものは、最も明白な 2 つの理由で C99 で未定義の動作を呼び出します。これは、同じシーケンス ポイント内で同じ変数を複数回変更することは許可されておらず、そのマクロは両方aをb複数回変更し、2 つ目はコンマ演算子を使用している場合です。
#define swap(a, b) (((a) ^ (b)) && ((b) ^= (a) ^= (b), (a) ^= (b)))
^
これはシーケンス ポイントを導入しますが、C99 のすべての未定義の動作を削除するわけではありません。これはb、 の値を計算するために の前の値が読み取られているaためbです。
C99 ドラフト標準セクション6.5 Expressionsパラグラフ2の関連セクションには、次のように書かれています (強調していきます):
前のシーケンス ポイントと次のシーケンス ポイントの間で、オブジェクトの格納値は、式の評価によって最大 1 回変更されます。72)さらに、以前の値は、保存する値を決定するためにのみ読み取られるものとします。73)
コンマ演算子については、セクション6.5.17 カンマ演算子の段落2から次のように述べています。
コンマ演算子の左側のオペランドは void 式として評価されます。その評価の後にシーケンスポイントがあります.[...]
最初の式が未定義である理由をよりよく理解するために、別の方法で説明します。
これは、C では部分式間の実行順序を制御できないためです。
a = a^(b=b^(a=a^b))
= の後の最初の a に対して、C コンパイラは a の初期値を使用するか、a の変更された値を使用するかを選択できます。したがって、それは明らかにあいまいであり、未定義の動作につながります。
2番目のものは、あいまいではないので、私には問題ないように見えます:
b = b ^(a=a^b)
a と b が式の最初の部分で発生するという事実は、(a^b)&&...&& によって最初の部分が最初に評価されるように強制されるため、私には問題に思えません。しかし、私は専門家ではないので、専門家に標準を分析してもらうことを好みます...