9

C標準の未定義動作の例の1つは、(J.2)を読み取ります。

—指定された添え字でオブジェクトに明らかにアクセスできる場合でも、配列の添え字が範囲外です(宣言int a[4][5]が与えられた左辺値式a[1][7]のように)(6.5.6)

宣言がからint a[4][5]に変更された場合でもunsigned char a[4][5]、アクセスするa[1][7]と未定義の動作が発生しますか?私の意見ではそうではありませんが、反対する人から聞いたことがあるので、SOの専門家になる他の人がどう思うか見てみたいと思います。

私の推論:

  • 6.2.6.1段落4および6.5段落7の通常の解釈では、オブジェクトの表現asizeof (unsigned char [4][5])*CHAR_BITビットであり、オブジェクトとオーバーラップする型の配列としてアクセスできunsigned char [20]ます。

  • a[1]unsigned char [5]は左辺値として型を持ちますが、式で([]演算子のオペランドとして、または同等にの+演算子のオペランドとして*(a[1]+7))使用されると、型のポインターに減衰しますunsigned char *

  • の値は、の形式a[1]の「表現」のバイトへのポインタでもあります。このように解釈すると、に7を追加することが有効です。aunsigned char [20]a[1]

4

5 に答える 5

4

J2 のこの「有益な例」は、標準化団体が何を望んでいたかのヒントとして読むことができます。配列インデックスの計算が誤って「表現配列」の境界内に何かを与えるという事実に依存しないでください。その意図は、すべての個々の配列境界が常に定義された範囲内にあるようにすることです。

特に、これにより、実装が積極的な境界チェックを実行し、コンパイル時または実行時にa[1][7].

この推論は、基になる型とは何の関係もありません。

于 2010-09-22T06:41:48.850 に答える
4

準拠するコンパイラを作成したいコンパイラ ベンダーは、標準の言うことには縛られていますが、あなたの推論には縛られていません。標準では、範囲外の配列添え字は例外なく未定義の動作であると述べているため、コンパイラは爆発することが許されています。

前回の議論からの私のコメントを引用するには ( C99 は配列が連続していることを保証しますか? )

「あなたの最初の質問はa[0][6]、宣言付きのchar a[5][5]でした。これは、何があっても UB です。 を使用char *p = &a[3][4];してアクセスp[0]することは有効ですp[5]。アドレスを取得すること&p[6]は引き続き有効ですが、アクセスp[6]はオブジェクトの外部、つまり UB です。アクセスa[0][6]は外部です。 char の array[5] 型を持つobject a[0]。結果の型は関係ありません。どのように到達するかが重要です。"

編集:

標準全体をスキャンし、事実を収集し、それらを組み合わせて、最終的に未定義の動作の結論に到達する必要がある、未定義の動作の十分なケースがあります。これは明示的であり、質問で標準の文を引用しています。これは明示的であり、回避策の余地はありません。

それが本当にUBであると私たちが確信するようになるために、どれだけ明確に推論できると思いますか?

編集2:

規格を掘り下げて情報を収集した後、別の関連する引用を次に示します。

6.3.2.1 - 3: sizeof 演算子または単項 & 演算子のオペランドである場合、または配列の初期化に使用される文字列リテラルである場合を除き、''array of type'' 型を持つ式は式に変換されます配列オブジェクトの最初の要素を指し、左辺値ではない型「型へのポインター」を使用します。配列オブジェクトにレジスタ ストレージ クラスがある場合、動作は未定義です。

したがって、これは有効だと思います:

unsigned char *p = a[1]; 
unsigned char c = p[7]; // Strict aliasing not applied for char types

これはUBです:

unsigned char c = a[1][7];

a[1]はこの時点では左辺値ではありませんが、さらに評価され、J.2 に違反して配列添字が範囲外になっているためです。実際に何が起こるかは、コンパイラが実際に多次元配列の配列インデックスをどのように実装するかによって異なります。したがって、既知のすべての実装で違いがないというのは正しいかもしれません。しかし、それは有効な未定義の動作でもあります。;)

于 2010-09-22T11:26:52.957 に答える
1

6.5.6/8から

ポインター オペランドと結果の両方が同じ配列 object の要素を指して いる場合、または 配列 objectの最後の要素の 1 つ後ろを指している場合、評価はオーバーフローを生成しません。それ以外の場合、動作は未定義です。

あなたの例では、a[1][7] は同じ配列オブジェクト a[1] を指していないか、a[1] の最後の要素の 1 つ後ろを指していないため、未定義の動作です。

于 2010-09-22T03:47:27.503 に答える
0

a[1][7]内部的には、実際の機械語では、とa[2][2]の定義に違いはありませんint a[4][5]。R ..が言ったように、これは配列アクセスが1 * sizeof(a[0]) + 7 = 122 * sizeof(a[0]) + 2 = 12* sizeof(int)もちろん)に変換されるためです。機械語は、配列、行列、またはインデックスについて何も知りません。それがアドレスについて知っているすべて。インデクサーに基づいて素朴な境界チェックを行うなど、好きなことを実行できる上記のCコンパイラーはa[1][7]、配列a[1]に8つのセルがないため、範囲外になります。この点で、とまたはの間に違いはありintませcharunsigned char

私の推測では、違いはとの間の厳密なエイリアシング規則にあると思いintますchar。プログラマーは実際には何も悪いことをしませんが、コンパイラーは配列に対して「論理」型キャストを実行する必要があります。Jens Gustedtが言ったように、これは厳密な境界チェックを有効にする方法のように見えますが、intまたはの実際の問題ではありませんchar

私はVC++コンパイラをいじってみましたが、期待どおりに動作するようです。誰かがこれをテストできますgccか?私の経験gccでは、この種のことについてははるかに厳格です。

于 2010-09-22T08:16:50.797 に答える
-1

引用された(J.2)サンプルが未定義の動作である理由は、リンカーがサブ配列a [1]、a[2]などをメモリ内で隣り合わせに配置する必要がないためだと思います。それらはメモリ全体に散在している可能性があります。または、隣接している可能性がありますが、期待される順序ではありません。基本型をintからunsignedcharに切り替えても、これは何も変わりません。

于 2010-09-22T03:23:04.760 に答える