なぜx [0]!= x [0] [0]!= x [0] [0] [0]なのですか?


149

私はC ++を少し勉強していて、ポインタを使って戦っています。次のように宣言することで、3レベルのポインタを使用できることを理解しています。

int *(*x)[5];

これ*xは、へのポインタである5つの要素の配列へのポインタintです。また、私はそれを知っているx[0] = *(x+0);x[1] = *(x+1)ようにして....

それで、上記の宣言を考えると、なぜx[0] != x[0][0] != x[0][0][0]ですか?


58
x[0]x[0][0]およびx[0][0][0]さまざまな種類があります。それらを比較することはできません。どういう意味!=ですか?
bolov 2015

4
@celticminstrelは同じではありません。5 int **x[5]つの要素の配列です。要素はint`へのポインターへのポインターです
bolov

5
@celticminstrel int** x[5]は、intを指すポインターを指す5つのポインターの配列になります。 int *(*x)[5]intを指す5つのポインタの配列へのポインタです。
emlai

5
@celticminstrel 右左規則スパイラル規則C 意味不明な↔英語、そしてあなたの3つ星プログラマーになるための道のり:)
bolov

5
@ Leo91:まず、ここには3 つではなく2つのレベルのポインターがあります。第二に、どういうx[0] != x[0][0] != x[0][0][0]意味ですか?これはC ++では有効な比較ではありません。場合でも、あなたはにそれを分割x[0] != x[0][0]し、x[0][0] != x[0][0][0]それはまだ有効ではありません。それで、あなたの質問はどういう意味ですか?
AnT 2015

回答:


261

xへの5つのポインタの配列へのポインタintです。への5つのポインタの
x[0]配列ですint
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]アドレスが含まれintているそれは0x100
  • x[0][0][0]int値が含まれています10

したがって、x[0]はに等しい&x[0][0]ため、&x[0][0] != x[0][0]です。
したがって、x[0] != x[0][0] != x[0][0][0]


この図は少し混乱します。ボックスの左側に表示されるのと同じように、0x100を含むボックスのすぐ左側に表示されます。左端ではなく、下にあります。100x500
2015

@MattMcNabb; 紛らわしいとは思いませんが、より明確にするために、提案に従って変更します。
2015

4
@haccks-私の喜び:)その図が素晴らしい理由は、あなたがそれに与えた説明さえ必要としないためです。その図自体は、すでに質問に答えていることから自明です。次のテキストは単なるボーナスです。
rayryeng 2015

1
作図ソフトウェアのyedを使用することもできます。それは私の考えを整理するために私を大いに助けます
rpax

@GrijeshChauhanコードのコメントにはasciiflowを使用し、プレゼンテーションにはyeDを使用します:)
rpax

133
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 [0]、x [0] [0]、x [0] [0] [0]が*(x + 0)、*(x + 0 + 0)と等しい場合、*(x + 0 + 0 + 0)、なぜそれらは異なるアドレスを持つ必要があるのですか?
Leo91

41
@ Leo91 x[0][0](x[0])[0]、つまり*((*(x+0))+0)、ではありません*(x+0+0)。間接参照は2番目の前に発生し[0]ます。
emlai

4
@ Leo91とx[0][0] != *(x+0+0)同じようにx[2][3] != x[3][2]
özg

@ Leo91「今すぐ入手」した2番目のコメントが削除されました。あなたは何かを理解していませんか(答えでよりよく説明できます)、またはこれはあなたの行動ではありませんか?(多くの有益なコンテンツなしでコメントを削除したい人もいます)
deviantfan

@deviantfan申し訳ありません、あなたの意味が理解できません。回答の理解と、metoが概念を明確にするのに役立つ多くのコメントを理解しています。
Leo91、2015

50

ポインタのメモリレイアウトは次のとおりです。

   +------------------+
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では実際には何も実行できないものです。常に配列全体を操作するのではなく、常にアドレスまたは要素を操作します。

  1. あなたは渡すことができますx[0]sizeofオペレータ。しかし、それは実際には値の使用ではなく、その結果はタイプのみに依存します。

  2. の値が得られるアドレスx、つまり、「配列のアドレス」をタイプで取得できますint*(*)[5]。言い換えると:&x[0] <=> &*(x + 0) <=> (x + 0) <=> x

  3. 、他のすべてのコンテキストの値は、x[0]配列の最初の要素へのポインタに減衰します。つまり、「配列のアドレス」という値と型を持つポインタint**です。結果は、x型のポインタにキャストした場合と同じですint**

ケース3の配列ポインターの減衰x[0]により、最終的にを使用すると、ポインター配列の先頭を指すポインターが生成されます。呼び出しprintf("%p", x[0])は、「address of array」というラベルの付いたメモリセルの内容を出力します。


1
x[0]配列のアドレスではありません。
2015

1
@haccksはい、標準の文字に従って、x[0]配列のアドレスではなく、それ自体が配列です。これについての詳細な説明x[0]と、「配列のアドレス」を書いた理由を追加しました。気に入ってくれるといいな。
cmaster-モニカを2015年

それを完全にうまく説明する素晴らしいグラフ!
MK

「しかし、配列はCでは実際には何もできないものです。」->カウンターの例:printf("%zu\n", sizeof x[0]);ポインターのサイズではなく、配列のサイズを報告します。
chux-モニカ

@ chux-ReinstateMonicaそして、「常にアドレス全体または要素のいずれかを操作し、配列全体を操作することはしない」と言い続け、次に列挙のポイント1でsizeof x[0]... の影響について説明します
cmaster-モニカ

18
  • x[0]最も外側のポインタ(間接参照ポインタへのポインタのサイズ5の配列のintへのポインタのサイズ5の配列に)、その結果をint
  • x[0][0]最も外側のポインタ逆参照し、配列にインデックスを付けますint。その結果、へのポインタになります。
  • x[0][0][0] すべてを逆参照し、具体的な値になります。

ちなみに、これらの種類の宣言が何を意味するのか混乱した場合は、cdeclを使用してください。


11

ステップバイステップ式x[0]x[0][0]およびを考えてみましょうx[0][0][0]

x次のように定義されます

int *(*x)[5];

次に、式x[0]はタイプの配列ですint *[5]。expression x[0]がexpression と同等であることを考慮してください*x。これは、配列へのポインタを逆参照することで、配列自体を取得します。宣言があるということをyのように示しましょう

int * y[5];

x[0][0]はと同等でy[0]あり、型を持っていint *ます。宣言があるということをzのように示しましょう

int *z;

expression x[0][0][0]は、expression と同等でtypeを持つy[0][0]expression 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;

10

最初に言わなければならないこと

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] = 10の値

x [0] [0] [0]のアドレスは1001です

そのアドレスはx [0] [0] = 1001に格納されます

x [0] [0]のアドレスは2000

そのアドレスはx [0] = 2000に保存されます

したがって、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 

3
あなたの答えは誤解を招くものです。x[0]アリの住所は含まれていません。その配列。それは最初の要素へのポインタに減衰します。
2015

うーん...それはどういう意味ですか?あなたの編集はあなたの間違った答えへのケーキのようなものです。意味がありません
15

@haccksポインタだけを使用している場合、この答えは正しいでしょう。Array
apm

7

配列を実際の視点から見ると、次のようになります。

x[0]木箱でいっぱいの貨物コンテナです。
x[0][0]貨物コンテナ内の靴箱がいっぱいの単一の箱です。
x[0][0][0]クレート内、貨物コンテナ内の単一の靴箱です。

貨物コンテナ内の唯一のクレート内の唯一の靴箱であったとしても、それはまだ靴箱であり、貨物コンテナではありません。


1
x[0][0]靴箱の場所が​​書かれた紙でいっぱいの単一の箱ではないでしょうか?
wchargin 2015

4

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

したがって、違いを理解することができます。


1
x[0]ポインタではなく、5つの整数の配列です。(ほとんどのコンテキストでポインターに減衰する可能性がありますが、ここでは区別が重要です)。
2015

さて、しかしあなたは言うべきです:x [0]は5つのポインタの配列int *
Nghia Bui

@MattMcNabbごとに正しい派生を与えるに*(*x)[5]は、はint、そう(*x)[5]ですint *、そう*xです(int *)[5]、そうxです*((int *)[5])です。つまりx、へのポインタの5つの配列へのポインタintです。
wchargin 2015

2

異なるタイプを値で比較しようとしています

あなたがアドレスを取るならば、あなたはあなたが期待するもののより多くを得るかもしれません

あなたの宣言は違いを生むことに注意してください

 int y [5][5][5];

以来、あなたが望む比較を可能にするyy[0]y[0][0]y[0][0][0]異なる値と型が、同じアドレスを持っているでしょう

int **x[5];

隣接するスペースを占有しません。

xx [0]同じアドレスを持っているがx[0][0]x[0][0][0]それぞれ異なるアドレスにある


2
int *(*x)[5]とは異なりますint **x[5]
MM

2

ビーイング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)

最初の文の後に何を表示しようとしていますか?のバリエーションはたくさんありますp == p&(&p[0])[0]とは異なりますp[0][0]
MM

男は、xがポインタであるとき、なぜ 'x [0]!= x [0] [0]!= x [0] [0] [0]'なのかと尋ねましたよね?私は彼が[0]をスタックしたときに逆参照(*)のC表記法によってトラップされる可能性があることを見せようとしました。したがって、xがx [0]と等しくなるように正しい表記を彼に示したり、&でx [0]を再度参照したりするなどの試みです。
Luciano、

1

他の答えは正しいですが、3つすべてに同じ値が含まれる可能性があるという考えを強調するものはありません。そのため、それらは何らかの形で不完全です。

他の回答からこれが理解できない理由は、すべてのイラストが、ほとんどの状況で有用かつ間違いなく合理的である一方で、ポインターxがそれ自体を指す状況をカバーできないためです。

これはかなり簡単に構築できますが、明らかに理解するのが少し難しいです。以下のプログラムでは、3つの値をすべて同一にする方法を示します。

注:このプログラムの動作は定義されていませんが、ここでは、ポインタができることの興味深いデモとして純粋にここに投稿していますが、すべきではありません

#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つの値はすべて同じです。しかし、これは驚くべきことではありません!まず、プログラムを分解してみましょう。

x5つの要素の配列へのポインタとして宣言します。各要素はintへのポインタ型です。この宣言は、ランタイムスタックに4バイトを割り当てます(または、実装に応じてそれ以上。私のマシンのポインターは4バイトです)。したがってx、実際のメモリの場所を参照しています。言語のCファミリでは、のコンテンツxは単なるガベージであり、以前の場所の使用から残されたものであるため、それx自体はどこにもポイントせず、割り当てられたスペースには間違いありません。

したがって、当然、変数のアドレスを取得しxてどこかに置くことができるので、それがまさに私たちが行うことです。しかし、先に進み、それをx自体に入れます。の&xタイプはとは異なるxため、警告を出さないようにキャストを行う必要があります。

メモリモデルは次のようになります。

0xbfd9198c
+------------+
| 0xbfd9198c |
+------------+

したがって、アドレスの4バイトのメモリブロックには0xbfd9198c、16進値に対応するビットパターンが含まれています0xbfd9198c。十分に単純です。

次に、3つの値を出力します。他の答えは、それぞれの表現が何を意味するかを説明しているので、関係は今明確になっているはずです。

値は同じであることがわかりますが、非常に低レベルの意味でのみ...ビットパターンは同じですが、各式に関連付けられた型データは、解釈された値が異なることを意味します。たとえばx[0][0][0]、フォーマット文字列を使用して印刷した場合%d、大きな負の数が得られるため、実際には「値」は異なりますが、ビットパターンは同じです。

これは実際には非常に単純です...図では、矢印は異なるメモリアドレスではなく同じメモリアドレスを指しています。ただし、予期しない結果を未定義の動作から強制することはできましたが、それだけです-未定義です。これは製品コードではなく、完全を期すための単なるデモンストレーションです。

妥当な状況では、を使用mallocして5つのintポインターの配列を作成し、再びその配列でポイントされるintを作成します。malloc常に一意のアドレスを返します(メモリが不足している場合を除き、その場合はNULLまたは0を返します)。そのため、このような自己参照ポインタについて心配する必要はありません。

うまくいけば、それがあなたが探している完全な答えです。あなたは期待すべきではありませんx[0]x[0][0]x[0][0][0]等しくなるように、しかし、強制場合、彼らは可能性があります。何か問題があった場合は、お知らせください。わかりやすくします。


私が今までに見たポインタの別の奇妙な使用法を言うでしょう。
2015

@haccksええ、それはかなり奇妙ですが、分解すると、他の例と同じくらい基本的です。それはたまたまビットパターンがすべて同じ場合です。
Purag 2015

コードにより未定義の動作が発生します。x[0]実際には正しいタイプの有効なオブジェクトを表していない
MM

@MattMcNabbそれは未定義です、そして私は実際にそれについて非常に明確です。タイプについては同意しません。xは配列へのポインタなので、[]演算子を使用してそのポインタからのオフセットを指定し、それを逆参照することができます。何がおかしいのですか?の結果x[0]は配列であり、それを使用し%pて印刷しても、Cは文句を言わないで実装されているため、文句を言わない。
Purag、2015

そして、これを-pedanticフラグでコンパイルしても警告は
出され

0

タイプがint *(*x)[5]あるint* (*)[5]つまりint型に5つのポインタの配列へのポインタ。

  • xintsへの5つのポインタの最初の配列のアドレス(型のアドレスint* (*)[5]
  • x[0]int型へのポインタ5の第1のアレイのアドレス(タイプと同じアドレスint* [5]()によるオフセットアドレスX 0*sizeof(int* [5])*サイズの型ビーイング・尖った-へと間接参照つまりインデックス)
  • x[0][0]配列内のintへの最初のポインタ(型と同じアドレスint*)(オフセットアドレスx by 0*sizeof(int* [5])およびdereference、次にby 0*sizeof(int*)およびdereference)
  • x[0][0][0]intへのポインタが指す最初のintです(オフセットアドレスxによる0*sizeof(int* [5])逆参照とオフセット、そのアドレスによる0*sizeof(int*)逆参照とオフセット、そのアドレスによる逆参照とオフセット0*sizeof(int))。

タイプがint *(*y)[5][5][5]あるint* (*)[5][5][5]つまりint型へのポインタ5x5x5の3D配列へのポインタ

  • x タイプを持つintへの5x5x5ポインタの最初の3D配列のアドレスです int*(*)[5][5][5]
  • x[0]intへの5x5x5ポインタの最初の3d配列のアドレスです(オフセットアドレスxによる0*sizeof(int* [5][5][5])参照と逆参照)。
  • x[0][0]intへの5x5ポインターの最初の2d配列のアドレスです(アドレスxをオフセットし0*sizeof(int* [5][5][5])、逆参照してからそのアドレスをオフセットします0*sizeof(int* [5][5])
  • x[0][0][0]intsへの5つのポインタの最初の配列のアドレスです(オフセットアドレスx by 0*sizeof(int* [5][5][5])と逆参照、そのアドレスの0*sizeof(int* [5][5])オフセット、そのアドレスのオフセット0*sizeof(int* [5])
  • x[0][0][0][0]配列内のintへの最初のポインターです(オフセットアドレスxによる0*sizeof(int* [5][5][5])逆参照とオフセット、そのアドレスによる0*sizeof(int* [5][5])オフセット、そのアドレスによる0*sizeof(int* [5])オフセット、そのアドレスによるオフセット0*sizeof(int*)と逆参照)。
  • x[0][0][0][0][0]intへのポインターが指す最初のintです(オフセットアドレスxによる0*sizeof(int* [5][5][5])逆参照とオフセット、そのアドレスによる0*sizeof(int* [5][5])オフセットとそのアドレスによる0*sizeof(int* [5])オフセットとそのアドレスによる0*sizeof(int*)オフセットと逆参照とそのアドレスによるオフセット0*sizeof(int)と逆参照によるオフセットと逆参照)。

配列の崩壊に関しては:

void function (int* x[5][5][5]){
  printf("%p",&x[0][0][0][0]); //get the address of the first int pointed to by the 3d array
}

これは、渡すことと同じであるint* x[][5][5]か、int* (*x)[5][5]後者にすべての崩壊を、すなわち彼ら。これがx[6][0][0]、関数での使用に関するコンパイラ警告が表示されない理由ですが、x[0][6][0]そのサイズ情報が保持されているために表示されます

void function (int* (*x)[5][5][5]){
  printf("%p",&x[0][0][0][0][0]); //get the address of the first int pointed to by the 3d array
}
  • x[0] intへの5x5x5ポインタの最初の3D配列のアドレスです
  • x[0][0] intへの5x5ポインタの最初の2d配列のアドレスです
  • x[0][0][0] intへの5つのポインタの最初の配列のアドレスです
  • x[0][0][0][0] 配列内のintへの最初のポインターです
  • x[0][0][0][0][0] intへのポインタが指す最初のintです

最後の例では、意味的にはでは*(*x)[0][0][0]なくを使用する方がはるかに明確ですx[0][0][0][0][0]。これは、最初と最後の[0]ここが型のために、多次元配列へのインデックスではなくポインター逆参照として解釈されるためです。ただし(*x) == x[0]、セマンティクスに関係なく同じです。を使用することもできます*****x。これは、ポインターを5回逆参照しているように見えますが、実際にはまったく同じように解釈されます。オフセット、逆参照、逆参照、配列への2つのオフセットと逆参照。操作を適用しています。

本質的に、あなた[0]または**非配列型である場合、それはの優先順位によるオフセットおよび逆参照です*(a + 0)

あなたとき[0]または配列型には、それがその後、冪等の間接参照をオフセットです( -それは冪等操作です逆参照は、同じアドレスを生成するコンパイラによって解決されます)。**

あなた[0]または*1D配列タイプのタイプの場合、それはオフセットであり、逆参照です

あなた[0]または**2D配列型の場合、それはオフセットのみ、つまりオフセットであり、べき等逆参照です。

あなた[0][0][0]または***3D配列型の場合、それはオフセット+べき等逆参照、次にオフセット+べき等逆参照、次にオフセット+べき等逆参照、そして逆参照です。真の逆参照は、配列型が完全に取り除かれた場合にのみ発生します。

int* (*x)[1][2][3]タイプの例では、順番にアンラップされます。

  • x タイプがあります int* (*)[1][2][3]
  • *xタイプがありますint* [1][2][3](オフセット0 + べき等逆参照)
  • **xタイプがありますint* [2][3](オフセット0 + べき等逆参照)
  • ***xタイプがありますint* [3](オフセット0 + べき等逆参照)
  • ****xタイプを持っているint*(オフセット0 +逆参照)
  • *****xタイプがありますint(オフセット0 +逆参照)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.