私はC++を少し勉強していて、ポインターを使って戦っています。私は宣言することで3つのレベルのポインタを持つことができることを理解しています:
int *(*x)[5];
*x
は、int
へのポインターである5つの要素の配列へのポインターです。また、x[0] = *(x+0);
、x[1] = *(x+1)
なども知っています。
したがって、上記の宣言を考えると、なぜx[0] != x[0][0] != x[0][0][0]
なのですか?
x
は、int
への5つのポインターの配列へのポインターです。x[0]
は配列ですint
への5つのポインター。x[0][0]
は、int
へのポインターです。x[0][0][0]
はint
です。
x[0]
Pointer to array +------+ x[0][0][0]
x -----------------> | | Pointer to int +-------+
0x500 | 0x100| x[0][0]----------------> 0x100 | 10 |
x is a pointer to | | +-------+
an array of 5 +------+
pointers to int | | Pointer to int
0x504 | 0x222| x[0][1]----------------> 0x222
| |
+------+
| | Pointer to int
0x508 | 0x001| x[0][2]----------------> 0x001
| |
+------+
| | Pointer to int
0x50C | 0x123| x[0][3]----------------> 0x123
| |
+------+
| | Pointer to int
0x510 | 0x000| x[0][4]----------------> 0x000
| |
+------+
あなたはそれを見ることができます
x[0]
は配列であり、式で使用されると、最初の要素へのポインターに変換されます(いくつかの例外があります)。したがって、x[0]
は、その最初の要素x[0][0]
である0x500
のアドレスを提供します。x[0][0]
には、0x100
であるint
のアドレスが含まれます。x[0][0][0]
には、10
のint
値が含まれます。したがって、x[0]
は&x[0][0]
と等しいため、&x[0][0] != x[0][0]
です。
したがって、x[0] != x[0][0] != x[0][0][0]
。
x[0] != x[0][0] != x[0][0][0]
あなた自身の投稿によると、
*(x+0) != *(*(x+0)+0) != *(*(*(x+0)+0)+0)`
簡略化されています
*x != **x != ***x
なぜそれが等しいのでしょうか?
最初のものは、あるポインターのアドレスです。
2番目は別のポインターのアドレスです。
3番目の値はint
値です。
ポインタのメモリレイアウトは次のとおりです。
+------------------+
x: | address of array |
+------------------+
|
V
+-----------+-----------+-----------+-----------+-----------+
| pointer 0 | pointer 1 | pointer 2 | pointer 3 | pointer 4 |
+-----------+-----------+-----------+-----------+-----------+
|
V
+--------------+
| some integer |
+--------------+
x[0]
は「配列のアドレス」を生成します。x[0][0]
は「ポインタ0」を生成します。x[0][0][0]
は、「整数」を生成します。
どうしてそれらがすべて違うのか、今は明らかだと思います。
上記は基本的な理解に十分近いため、私が書いたとおりに書いたのはこのためです。ただし、ハックが正しく指摘しているように、最初の行は100%正確ではありません。それで、ここにすべての詳細があります:
C言語の定義から、x[0]
の値は整数ポインターの配列全体です。ただし、配列はCでは実際には何もできないものです。アドレスまたは要素のいずれかを常に操作し、配列全体を操作することはありません。
x[0]
をsizeof
演算子に渡すことができます。しかし、それは実際には値の使用ではなく、結果は型のみに依存します。
x
の値を生成するアドレスを取得できます。 e。タイプint*(*)[5]
の「配列のアドレス」。つまり、&x[0] <=> &*(x + 0) <=> (x + 0) <=> x
他のすべてのコンテキストでは、x[0]
の値は、配列の最初の要素へのポインターに減衰します。つまり、値「配列のアドレス」とタイプint**
を持つポインター。効果は、x
をint**
型のポインターにキャストした場合と同じです。
ケース3の配列ポインターの減衰により、x[0]
を使用すると、最終的にポインター配列の先頭を指すポインターになります。呼び出しprintf("%p", x[0])
は、「配列のアドレス」としてラベル付けされたメモリセルの内容を出力します。
x[0]
は、最も外側のポインター(pointerintへのポインターのサイズ5の配列)を逆参照し、結果としてポインター5のサイズ5の配列になりますint
;x[0][0]
は、最も外側のポインターを逆参照しand配列のインデックスを作成し、int
へのポインターを作成します。x[0][0][0]
はすべてを間接参照し、具体的な値をもたらします。ところで、これらの種類の宣言の意味に混乱を感じる場合は、 cdecl を使用してください。
段階的な式x[0]
、x[0][0]
、およびx[0][0][0]
を検討してみましょう。
x
は次のように定義されているため
int *(*x)[5];
式x[0]
はint *[5]
型の配列です。式x[0]
は式*x
と同等であることを考慮してください。これは、配列へのポインターを逆参照することで、配列自体を取得します。 yのようにそれを示しましょう
int * y[5];
式x[0][0]
はy[0]
と同等で、タイプはint *
です。宣言があるzのようにそれを示しましょう
int *z;
式x[0][0][0]
は式y[0][0]
と同等であり、式z[0]
と同等であり、タイプはint
です。
だから私たちは
x[0]
のタイプはint *[5]
です
x[0][0]
のタイプはint *
です
x[0][0][0]
のタイプはint
です
したがって、それらはさまざまなタイプのオブジェクトであり、さまざまなサイズです。
たとえば実行
std::cout << sizeof( x[0] ) << std::endl;
std::cout << sizeof( x[0][0] ) << std::endl;
std::cout << sizeof( x[0][0][0] ) << std::endl;
最初に私が言わなければならないこと
x [0] = *(x + 0)= * x;
x [0] [0] = *(*(x + 0)+ 0)= * * x;
x [0] [0] [0] = *(*(*(x + 0)+ 0))= * * * x;
したがって、* x≠* * x≠* * * x
次の図から、すべてのことが明らかです。
x[0][0][0]= 2000
x[0][0] = 1001
x[0] = 10
これは単なる例であり、値はx [0] [0] [0] = 1
x [0] [0] [0]のアドレスは1001
そのアドレスはx [0] [0] = 1001に保存されます
x [0] [0]のアドレスは20
そのアドレスはx [0] = 20に保存されます
x [0] [0] [0]≠x [0] [0]≠- x [0]
。
編集
プログラム1:
{
int ***x;
x=(int***)malloc(sizeof(int***));
*x=(int**)malloc(sizeof(int**));
**x=(int*)malloc(sizeof(int*));
***x=10;
printf("%d %d %d %d\n",x,*x,**x,***x);
printf("%d %d %d %d %d",x[0][0][0],x[0][0],x[0],x,&x);
}
出力
142041096 142041112 142041128 10
10 142041128 142041112 142041096 -1076392836
プログラム2:
{
int x[1][1][1]={10};
printf("%d %d %d %d \n ",x[0][0][0],x[0][0],x[0],&x);
}
出力
10 -1074058436 -1074058436 -1074058436
現実世界の観点から配列を表示すると、次のように表示されます。
x[0]
は、木枠でいっぱいの貨物コンテナです。x[0][0]
は、貨物コンテナ内にある靴箱で満たされた単一の箱です。x[0][0][0]
は、クレート内、貨物コンテナ内の単一の靴箱です。
たとえ貨物コンテナ内の唯一のクレートにある唯一の靴箱であったとしても、それはまだ貨物箱ではなく靴箱です
C++には次のような原則があります。変数の宣言は、変数の使用方法を正確に示します。あなたの宣言を考慮してください:
int *(*x)[5];
(より明確にするために)書き換えることができます:
int *((*x)[5]);
原則により、次のものがあります。
*((*x)[i]) is treated as an int value (i = 0..4)
→ (*x)[i] is treated as an int* pointer (i = 0..4)
→ *x is treated as an int** pointer
→ x is treated as an int*** pointer
したがって:
x[0] is an int** pointer
→ x[0][0] = (x[0]) [0] is an int* pointer
→ x[0][0][0] = (x[0][0]) [0] is an int value
そのため、違いを理解できます。
p
ポインターであること:p[0][0]
と逆参照をスタックしています。これは*((*(p+0))+0)
と同等です。
C参照(&)および逆参照(*)表記では:
p == &p[0] == &(&p[0])[0] == &(&(&p[0])[0])[0])
以下と同等です:
p == &*(p+0) == &*(&*(p+0))+0 == &*(&*(&*(p+0))+0)+0
見てください、&*はリファクタリングでき、削除するだけです:
p == p+0 == p+0+0 == p+0+0+0 == (((((p+0)+0)+0)+0)+0)
他の答えは正しいですが、3つすべてに同じ値を含めることができるという考えを強調していないため、何らかの形で不完全です。
これが他の回答から理解できない理由は、すべての図が、ほとんどの状況で役に立ち、間違いなく合理的であるにもかかわらず、ポインターx
がそれ自体を指している状況をカバーできないためです。
これは構築するのはかなり簡単ですが、明らかに理解するのが少し難しいです。次のプログラムでは、3つの値すべてを強制的に同一にする方法を確認します。
注:このプログラムの動作は未定義ですが、ポインタcan行いますが、すべきではありません。
#include <stdio.h>
int main () {
int *(*x)[5];
x = (int *(*)[5]) &x;
printf("%p\n", x[0]);
printf("%p\n", x[0][0]);
printf("%p\n", x[0][0][0]);
}
これはC89とC99の両方で警告なしにコンパイルされ、出力は次のとおりです。
$ ./ptrs
0xbfd9198c
0xbfd9198c
0xbfd9198c
興味深いことに、3つの値はすべて同じです。しかし、これは驚くべきことではありません!まず、プログラムを分析しましょう。
x
を、各要素がintへのポインター型である5つの要素の配列へのポインターとして宣言します。この宣言は、ランタイムスタックに4バイトを割り当てます(実装によって異なります。マシンポインターは4バイトです)。したがって、x
は実際のメモリの場所を指します。 Cファミリーの言語では、x
の内容は単なるゴミであり、以前の場所の使用から残ったものなので、x
自体はどこにも指しません。
したがって、当然のことながら、変数x
のアドレスを取得してどこかに配置することができます。しかし、先に進み、それをx自体に入れます。 &x
の型はx
とは異なるため、警告を受け取らないようにキャストする必要があります。
メモリモデルは次のようになります。
0xbfd9198c
+------------+
| 0xbfd9198c |
+------------+
したがって、アドレス0xbfd9198c
の4バイトのメモリブロックには、16進値0xbfd9198c
に対応するビットパターンが含まれています。簡単です。
次に、3つの値を出力します。他の回答では、各表現が何を指しているのかを説明しているので、関係は明確になりました。
値が同じであることがわかりますが、非常に低レベルの意味でのみです...ビットパターンは同一ですが、各式に関連付けられた型データは、解釈された値が異なることを意味します。たとえば、フォーマット文字列x[0][0][0]
を使用して%d
を出力すると、大きな負の数が得られるため、実際には「値」は異なりますが、ビットパターンは同じです。
これは実際には本当に簡単です...図では、矢印は異なるメモリアドレスではなく、同じメモリアドレスを指しているだけです。ただし、未定義の動作から予期される結果を強制することはできましたが、それは未定義です。これは製品コードではなく、完全を期すための単なるデモンストレーションです。
合理的な状況では、malloc
を使用して5つのintポインターの配列を作成し、再びその配列でポイントされるintを作成します。 malloc
は常に一意のアドレスを返します(メモリが不足している場合を除き、NULLまたは0を返します)。そのため、このような自己参照ポインターを心配する必要はありません。
うまくいけば、それがあなたが探している完全な答えです。 x[0]
、x[0][0]
、およびx[0][0][0]
が等しいことを期待するべきではありませんが、強制された場合は同じになる可能性があります。あなたの頭の上に何かがあったら、私に知らせてください!
値によって異なるタイプを比較しようとしています
アドレスを取得すると、期待以上の結果が得られる可能性があります
あなたの宣言が違いを生むことに留意してください
int y [5][5][5];
y
、y[0]
、y[0][0]
、y[0][0][0]
は異なる値と型を持ちますが、同じアドレスを持つため、必要な比較が可能になります
int **x[5];
連続したスペースを占有しません。
x
とx [0]
は同じアドレスですが、x[0][0]
とx[0][0][0]
はそれぞれ異なるアドレスにあります