34

私は型崩壊の性質を理解しようとしています。たとえば、配列が特定のコンテキストでポインタに減衰することは誰もが知っています。int[]私の試みは、2次元配列がどのように等しいかを理解することですint*が、2次元配列が期待されるポインター型にどのように対応していないかを理解することです。テストケースは次のとおりです。

std::is_same<int*, std::decay<int[]>::type>::value; // true

これは期待どおりにtrueを返しますが、そうではありません。

std::is_same<int**, std::decay<int[][1]>::type>::value; // false

なぜこれは真実ではないのですか?私はついにそれをtrueに戻す方法を見つけました。それは、最初の次元をポインターにすることでした。

std::is_same<int**, std::decay<int*[]>::type>::value; // true

そして、アサーションは、ポインターを持つすべてのタイプに当てはまりますが、最後は配列です。たとえば(int***[] == int****; // true)。

なぜこれが起こっているのか説明してもらえますか?予想どおり、配列型がポインタ型に対応しないのはなぜですか?

4

4 に答える 4

63

なぜint*[]崩壊するのに崩壊しint**ないのint[][]ですか?

それでポインタ演算を行うことは不可能だからです。

たとえば、int p[5][4]は(の長さ-4の配列int)の配列を意味します。関係するポインタはありません。それは単にサイズのメモリの連続したブロックです5*4*sizeof(int)。たとえばint a = p[i][j]、特定の要素を要求すると、コンパイラは実際にこれを実行します。

char *tmp = (char *)p           // Work in units of bytes (char)
          + i * sizeof(int[4])  // Offset for outer dimension (int[4] is a type)
          + j * sizeof(int);    // Offset for inner dimension
int a = *(int *)tmp;            // Back to the contained type, and dereference

明らかに、これを実行できるのは、「内部」ディメンションのサイズを認識しているためです。にキャストすると、int (*)[4]この情報が保持されます。(長さ-4の配列int)へのポインタです。ただし、そうではありint ** ません; それは単にへのポインタです(へのポインタint)。

これに関する別の見方については、CFAQの次のセクションを参照してください。

(これはすべてCの場合ですが、この動作はC ++では基本的に変更されていません。)

于 2013-01-06T15:22:49.757 に答える
10

Cは実際には言語として「設計」されていませんでした。代わりに、以前のコードを壊さないように、必要に応じて機能が追加されました。このような進化的アプローチは、Cが開発されていた時代には良いことでした。なぜなら、ほとんどの場合、開発者は、言語が行う必要のあるすべてが解決される前に、言語の以前の改善のメリットを享受できるからです。残念ながら、配列とポインタの処理が進化した方法により、さまざまなルールが作成されましたが、振り返ってみると、残念なことです。

今日のC言語には、かなり実質的な型システムがあり、変数には明確に定義された型がありますが、必ずしもそうとは限りませんでした。宣言char arr[8]; 現在のスコープに8バイトを割り当てarr、最初のバイトをポイントします。arrコンパイラは、それが配列を表していることを知りません。他のと同じように、charポインタを表しますchar*。私が理解していることから、もし宣言したとしたらchar arr1[8], arr2[8];、そのステートメントarr1 = arr2;は完全に合法であり、概念的にはとある程度同等char *st1 = "foo, *st2 = "bar"; st1 = st2;でしたが、ほとんどの場合、バグを表していたでしょう。

配列がポインターに分解されるという規則は、配列とポインターが実際には同じものであった時代に由来します。それ以来、配列は別個の型として認識されるようになりましたが、言語は、配列がそうでなかった時代と本質的に互換性を保つ必要がありました。ルールが策定されていたとき、二次元配列をどのように扱うべきかという問題は、そのようなことはなかったので問題ではありませんでした。2次元配列を使用するのと同じように、次のようなことを実行して使用することもできますchar foo[20]; char *bar[4]; int i; for (i=0; i<4; i++) bar[i] = foo + (i*5);が、コンパイラはそのように表示しません。ポインタへのポインタとしてのみ表示されます。foo[1]がfoo[2]とは完全に異なる場所を指すようにしたい場合は、完全に合法的にそうすることができます。bar[x][y]bar

2つの2次元配列がCに追加されたとき、2次元配列を宣言した以前のコードとの互換性を維持する必要はありませんでした。char bar[4][5];を使用して表示されたものと同等のコードを生成するように指定することは可能でしたがfoo[20]、その場合、achar[][]はとして使用できましたがchar**、配列変数の割り当てが99%の確率で間違いであったと考えられていました。 、それが合法であったなら、配列行の再割り当てもそうだったでしょう。したがって、Cの配列は、少し奇妙な独自のルールを持つ別個の型として認識されますが、それはそれらが何であるかです。

于 2013-01-06T17:37:07.053 に答える
8

int[M][N]int**は互換性のないタイプであるため。

ただし、タイプint[M][N]に崩壊する可能性があります。int (*)[N]したがって、次のようになります。

std::is_same<int(*)[1], std::decay<int[1][1]>::type>::value;

あなたに与えるべきですtrue

于 2013-01-06T15:24:35.457 に答える
3

2次元配列は、ポインタへのポインタとしてではなく、連続したメモリブロックとして格納されます。

タイプとして宣言されたオブジェクトint[y][x]はサイズのブロックですが、sizeof(int) * x * yタイプのオブジェクトint **int*

于 2013-01-06T15:30:12.670 に答える