このC99コードは未定義の動作を生成しますか?
#include <stdio.h>
int main() {
int a[3] = {0, 0, 0};
a[a[0]] = 1;
printf("a[0] = %d\n", a[0]);
return 0;
}
ステートメントでa[a[0]] = 1;
、a[0]
は読み取りと変更の両方が行われます。
ISO/IEC 9899のn1124ドラフトを見ました。(6.5式で)次のように書かれています。
前のシーケンスポイントと次のシーケンスポイントの間で、オブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。さらに、前の値は、格納される値を決定するために読み取り専用でなければなりません。
変更するオブジェクト自体を決定するためにオブジェクトを読み取ることについては言及されていません。したがって、このステートメントは未定義の動作を生成する可能性があります。
しかし、私はそれを奇妙に感じます。これは実際に未定義の動作を生成しますか?
(他のISO Cバージョンでもこの問題について知りたいです。)
格納する値を決定するために、前の値を読み取り専用にします。
これは少し曖昧で混乱を引き起こしました。そのため、C11はそれを破棄し、新しいシーケンスモデルを導入しました。
それが言おうとしているのは、古い値の読み取りが新しい値の書き込みよりも早い時間に行われることが保証されている場合、それは問題ないということです。それ以外の場合はUBです。そしてもちろん、新しい値を書き込む前に計算する必要があります。
(もちろん、私が今書いた説明は、標準のテキストよりも曖昧であることがわかるでしょう!)
たとえば、最初にx
を知らなければx = x + 5
を計算することはできないため、x + 5
は正しいです。ただし、i
に格納する新しい値を計算するために、左側のi
を読み取る必要がないため、a[i] = i++
は間違っています。 (i
の2つの読み取りは別々に考慮されます)。
今すぐコードに戻ります。配列インデックスを決定するためのa[0]
の読み取りは、書き込みの前に行われることが保証されているため、これは明確に定義された動作だと思います。
どこに書くか決めるまで書くことはできません。そして、a[0]
を読むまで、どこに書くべきかわかりません。したがって、読み取りは書き込みの前に行う必要があるため、UBはありません。
誰かがシーケンスポイントについてコメントしました。 C99では、この式にはシーケンスポイントがないため、シーケンスポイントはこの説明には含まれません。
このC99コードは未定義の動作を生成しますか?
いいえ。未定義の動作は発生しません。 a[0]
は、2つの間で1回だけ変更されます シーケンスポイント (最初のシーケンスポイントは初期化子int a[3] = {0, 0, 0};
の終わりにあり、2番目は完全な式a[a[0]] = 1
の後にあります)。
変更するオブジェクト自体を決定するためにオブジェクトを読み取ることについては言及されていません。したがって、このステートメントは未定義の動作を生成する可能性があります。
オブジェクトを複数回読み取って、オブジェクト自体とその完全に定義された動作を変更できます。この例を見てください
int x = 10;
x = x*x + 2*x + x%5;
引用の2番目のステートメントは次のように述べています。
さらに、前の値は、格納する値を決定するためにのみ読み取られるものとします。
上記の式のすべてのx
が読み取られ、オブジェクトx
自体の値が決定されます。
注:質問で言及されている引用には2つの部分があることに注意してください。最初の部分は次のように述べています:前のシーケンスポイントと次のシーケンスポイントの間で、オブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。、および
したがって、次のような表現
i = i++;
uB(前のシーケンスポイントと次のシーケンスポイントの間の2つの変更)に分類されます。
2番目の部分は次のように述べています:さらに、前の値は、格納される値を決定するためにのみ読み取られる必要があります。、したがって、次のような式
a[i++] = i;
j = (i = 2) + i;
uBを呼び出します。どちらの式でも、i
は前のシーケンスポイントと次のシーケンスポイントの間で1回だけ変更されますが、右端のi
の読み取りは、i
に格納される値を決定しません。
C11標準では、これはに変更されました
スカラーオブジェクトの副作用がシーケンスされていない場合同じスカラーオブジェクトの異なる副作用または同じ値を使用した値の計算スカラーオブジェクト、動作は未定義です。 [...]
式a[a[0]] = 1
では、a[0]
の副作用は1つだけであり、インデックスa[0]
の値の計算は、a[a[0]]
の値の計算の前にシーケンスされます。
C99は、付録Cのすべてのシーケンスポイントの列挙を示します。最後に1つあります。
a[a[0]] = 1;
これは完全な式ステートメントですが、内部にシーケンスポイントがないためです。ロジックでは、部分式a[0]
を最初に評価し、その結果を使用して値を割り当てる配列要素を決定する必要がありますが、シーケンス規則では保証されません。 a[0]
の初期値が0
の場合、a[0]
は2つのシーケンスポイント間で読み取りと書き込みの両方が行われ、書き込む値を決定するために読み取りはnotになります。したがって、C99 6.5/2によると、式を評価する動作は定義されていませんが、実際には、心配する必要はないと思います。
この点ではC11の方が優れています。セクション6.5、パラグラフ(1)は言う
式は、値の計算を指定するか、オブジェクトまたは関数を指定するか、副作用を生成するか、またはそれらの組み合わせを実行する演算子とオペランドのシーケンスです。演算子のオペランドの値の計算は、演算子の結果の値の計算の前にシーケンスされます。
特に、C99に類似物がない2番目の文に注意してください。それで十分だと思うかもしれませんが、そうではありません。 値の計算に適用されますが、値の計算に関連する副作用の順序については何も述べていません。左のオペランドの値を更新することは副作用であるため、余分な文は直接適用されません。
それにもかかわらず、代入演算子の仕様が必要な順序付けを提供するため、C11はこれで私たちのためにやって来ます(C11 6.5.16(3)):
[...]左のオペランドの格納された値を更新することの副作用は、左と右のオペランドの値の計算の後にシーケンスされます。オペランドの評価は順序付けられていません。
(対照的に、C99は、左のオペランドの格納された値の更新が前のシーケンスポイントと次のシーケンスポイントの間で発生することを示しています。)セクション6.5と6.5.16を一緒に使用すると、C11は明確に定義されたシーケンスを提供します。内部の[]
が評価されます。保存された値が更新される前に評価される外部[]
の前。これはC11のバージョン6.5(2)を満たしているため、C11では式を評価する動作が定義されています。
a[0]
に有効な配列インデックスではない値が含まれていない限り(つまり、コード内で負ではなく、3
を超えない場合)、値は明確に定義されています。コードをより読みやすく同等のものに変更できます
index = a[0];
a[index] = 1; /* still UB if index < 0 || index >= 3 */
式a[a[0]] = 1
では、最初にa[0]
を評価する必要があります。 a[0]
がたまたまゼロの場合、a[0]
が変更されます。ただし、コンパイラが(標準に準拠していない場合を除いて)評価の順序を変更し、値を読み取ろうとする前にa[0]
を変更する方法はありません。
副作用には、オブジェクトの変更が含まれます1。
C標準では、オブジェクトへの副作用が同じオブジェクトへの副作用または同じオブジェクトの値を使用した値の計算でシーケンスされていない場合、動作は未定義であるとされています。2。
この式のオブジェクトa[0]
が変更され(副作用)、その値(値の計算)がインデックスの決定に使用されます。この式は未定義の動作をもたらすように思われます。
a[a[0]] = 1
ただし、標準の代入演算子のテキストでは、演算子=
の左右両方のオペランドの値の計算は、左側のオペランドが変更される前にシーケンスされると説明されています。3。
したがって、動作は最初のルールとして定義されます1 変更(副作用)は同じオブジェクトの値の計算後にシーケンスされるため、違反はありません。
1 (ISO/IEC 9899:201x 5.1.2.3プログラム実行2から引用):
揮発性オブジェクトへのアクセス、オブジェクトの変更、ファイルの変更、またはこれらの操作のいずれかを実行する関数の呼び出しはすべて副作用であり、実行環境の状態の変化です。
2 (ISO/IEC 9899:201x 6.5式2から引用):
スカラーオブジェクトの副作用が、同じスカラーオブジェクトの異なる副作用、または同じスカラーオブジェクトの値を使用した値の計算に比べて順序付けされていない場合、動作は定義されていません。
3 (ISO/IEC 9899:201x 6.5.16代入演算子3から引用):
左のオペランドの格納された値を更新することの副作用は、左と右のオペランドの値の計算の後にシーケンスされます。オペランドの評価は順序付けられていません。