あなたの理解はやや正しいですが、あまりにも多くのものを混ぜ合わせているようで、図には詳細が欠けています。これが私が最も単純なケースのいくつかのためにそれを描く方法です...
new
単純なケースから始めて、オペレーターを写真から外してみましょう。
#include <cstdio>
struct myobj {
int v;
};
int main()
{
myobj obj[2];
obj[0].v = 1;
obj[1].v = 2;
myobj *ptra = &obj[0];
myobj *ptrb = &obj[1];
myobj **ptrc = &ptrb;
printf("obj size is: %lu\n", sizeof(myobj));
printf("pointer size: %lu\n", sizeof(void *));
printf("obj[0] address: %p\n", (void *)&obj[0]);
printf("obj[1] address: %p\n", (void *)&obj[1]);
printf("ptra address is %p, it points to %p\n", (void *)&ptra, (void *)ptra);
printf("ptrb address is %p, it points to %p\n", (void *)&ptrb, (void *)ptrb);
printf("ptrc address is %p, it points to %p\n", (void *)&ptrc, (void *)ptrc);
}
上記のプログラムは次のようなものを出力します:
$ g++ -Wall -pedantic -o test ./test.cpp
$ ./test
obj size is: 4
pointer size: 8
obj[0] address: 0x7fff5b73dbc0
obj[1] address: 0x7fff5b73dbc4
ptra address is 0x7fff5b73dbb8, it points to 0x7fff5b73dbc0
ptrb address is 0x7fff5b73dbb0, it points to 0x7fff5b73dbc4
ptrc address is 0x7fff5b73dba8, it points to 0x7fff5b73dbb0
これは、メモリ内の次の単純なレイアウトに対応します。
それで、あなたの絵と何が違うのですか?ポインタとオブジェクトのアドレス。ポインタ自体がアドレス0に配置されている場合、ポインタ自体がより多くのスペースを使用するという理由だけで次のポインタをアドレス1に配置できないため、他のデータは0 + sizeof(void*)
アドレスにのみ配置できます。オブジェクトの場合、次のアドレスは、少なくともオブジェクト自体のサイズ(つまりsizeof(myobj)
)だけ大きくなります。
動的割り当てが関係する場合、状況は少し変わります。たとえば、演算子「new」を使用して次のようなオブジェクトを割り当てる場合:
myobj *ptra = new myobj();
myobj *ptrb = new myobj();
myobj **ptrc = &ptrb;
...次のようなメモリレイアウトを考えることができます。
ここで、別のポインター(**)へのポインターは、オブジェクトを指す1つ以上のポインターの最初のものを指すポインターに他なりません。簡単ですよね?ポインタへのポインタを持つことができます...とにかく、次のように、ポインタへの動的に割り当てられたポインタを使用します。
myobj *ptra = new myobj();
myobj *ptrb = new myobj();
myobj **ptrc = new myobj*[2];
ptrc[0] = ptra;
ptrc[1] = ptrb;
メモリレイアウトは次のようになります。
ちなみに、ここの3行目にエラーがあります- myobj **ptrc = new *myobj();
。する必要がありますmyobj **ptrc = new myobj*();
。
myobj *ptrb = new myobj[2]();
後の質問に対処するために、動的に割り当てられた2つのオブジェクトを指すポインターがある式の結果を示す図を以下に示します。ポインタ自体は、割り当てられた2つのうち最初のオブジェクトを指します。
そして、違いを確認できるように、ポインタへのポインタについてもう一度説明します。次のコードを検討してください。
struct myobj {
int v;
};
int main()
{
myobj *ptra = new myobj[2]();
myobj *ptrb = new myobj[4]();
myobj **ptrc = new myobj*[2];
ptrc[0] = ptra;
ptrc[1] = ptrb;
ptrc[0][0].v = 1;
ptrc[0][1].v = 2;
ptrc[1][0].v = 3;
ptrc[1][1].v = 4;
ptrc[1][2].v = 5;
ptrc[1][3].v = 6;
}
次のレイアウトが作成されます。
ご覧のとおり、スタックには動的に割り当てられていない3つのポインター(これらはオブジェクトでもあります)が含まれています。これは宣言の結果です:
myobj *ptra;
myobj *ptrb;
myobj **ptrc;
次に、3つの異なるものが「新規」で割り当てられます。
- タイプの2つのオブジェクトが式
myobj
で割り当てられnew myobj[2]()
、それらのオブジェクトの最初のオブジェクトへのアドレスがポインタに格納されますptra
。
- タイプの4つのオブジェクトが式
myobj
で割り当てられnew myobj[4]()
、その式の結果は4つのオブジェクトのうちの最初のアドレスであり、ポインター「ptrb」に格納されます。
- 2つのポインタが式で割り当てられます
new myobj*[2]
。その式の結果は、2つのポインターのうちの最初のアドレスです。そのアドレスは変数に格納されますptrc
。
ここで、(「ブロックC」内の)これらの2つの割り当てられたポインターは、「どこにも」を指しません。ptra
したがって、例として、ptrb
ポインタを「値で」コピーすることで、同じオブジェクトをポイントし、ポイントしているようにします。
ptrc[0] = ptra;
ptrc[1] = ptrb;
とても簡単です!