2013-01-06 4 views
33

私はタイプディケイの性質を理解しようとしています。たとえば、配列は特定のコンテキストでポインタに崩壊することをすべて知っています。私の試みはint[]int*とどのように等しいかを理解することですが、2次元配列がどのようにして予想されるポインタ型に対応しないのかを理解することです。ここではテストケースである:なぜint * []はint **に崩壊しますが、int [] []ではなくなりますか?

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)です。

なぜこのようなことが起こっているのか説明できますか?配列型がポインタ型に対応しないのはなぜですか?

答えて

61

はなぜint**なくint[][]int*[]崩壊していますか?

ポインタ演算を行うことが不可能なためです。

たとえば、int p[5][4]は、(長さ4の配列int)の配列を意味します。ポインタは含まれていません。単にサイズが5*4*sizeof(int)の連続したメモリブロックです。あなたが特定の要素を求めるとき、例えばint a = p[i][j]、コンパイラは実際にこれをやっている:それは「内側」の次元(S)の大きさを知っているので、

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へのポインタ)です。

(これは、Cのために、すべてのですが、この振る舞い:別の場合

はCよくある質問の次のセクションを参照してください、これに取りますC++では基本的に変更されていません)。

+3

+1。いい答えだ。 – Nawaz

+0

+1わかりやすい説明。 –

+0

これを見る別の方法は 'int [M] [N]'〜 'int **'に1回の変換(許可される)とは対照的に2回の変換(許可されない)が必要です。最初の変換では、 'int [M] [N]'から*ポインタ*に変換して配列の最初の要素に変換する必要があります。最初の要素の型は 'int [N]'なので、 'int [M] [N]'はまず 'int(*)[N]'に変換します。 *内部*配列 'int [N]'の最初の要素は 'int *'に変換されます。 – Nawaz

7

Be原因:int[M][N]int**は互換性のない種類です。

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

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

trueになります。

+1

かなり簡単で素敵な答えナワズ! –

3

2次元配列は、ポインタへのポインタとしてではなく、連続したメモリブロックとして格納されます。タイプint **の目的は、実際の言語のように「設計」されなかったint*

+1

正確には、「型int [y] [x] '**として宣言されたオブジェクトは、サイズが「sizeof(int)* x * y」のブロックです。 –

10

Cへのポインタであり、一方

タイプint[y][x]として宣言されたオブジェクトは、サイズsizeof(int) * x * yのブロックです。代わりに、必要に応じて機能が追加され、以前のコードを破棄しないようにしました。このような進化的アプローチは、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);のようなものを実行し、次にbar[x][y]を2次元配列を使用するのと同じ方法で使用することができますが、コンパイラはこれを見ないでしょう - ちょうどbarをポインタへのポインタとして見ただけです。 foo [1]がfoo [2]とは全く別の場所を指し示すようにしたければ、完全に合法的に行うことができます。

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

+0

これは興味深いですが、これについての参考資料はありますか? (これは本物の質問です、私は本当にこれの歴史について読むことに興味があります。) –

+0

残念なことに、歴史の様々なビットが他のSOの質問で議論されていますが、私は単一のリファレンスを持っていません。 – supercat

+0

クレームをサポートするために少なくとも2つのSOスレッドがありますか?ニースポスト。 – SparKot

関連する問題