ポインタの「逆参照」とはどういう意味ですか?


541

説明に例を含めてください。


これはあなたを助けることができる:stackoverflow.com/questions/2795575/...
ハリー・ジョイ


24
int *p;整数へのポインターを定義し、*pそのポインターを逆参照します。つまり、pが指すデータを実際に取得します。
ペイマン、2011

4
Binky's Pointer Fun(cslibrary.stanford.edu/104)は、物事を明確にする可能性のあるポインターに関する素晴らしいビデオです。@ Erik- Stanford CS Libraryリンクを張ってくれたのは素晴らしい。そこには非常に多くの利点があります...
templatetypedef

6
ハリーの応答は、ここで役立つのとは逆です。
ジムバルター、2011

回答:


731

基本的な用語の復習

それはだ、通常は十分に良い-あなたがアセンブリプログラミングをしている場合を除き-想定するポインタ 1が2、第三の3第四ので、プロセスのメモリ内に第二のバイトを参照して、数値のメモリアドレスを含むが....

  • 0と最初のバイトはどうなりましたか?後で説明します。以下のnullポインタを参照してください。
  • ポインタが格納する内容、およびメモリとアドレスの関係のより正確な定義については、この回答の最後にある「メモリアドレスの詳細と、おそらく知る必要がない理由」を参照してください。

ポインタが指すメモリ内のデータ/値(その数値インデックスを持つアドレスの内容)にアクセスする場合は、ポインタを逆参照します。

コンピューター言語が異なれば表記が異なり、コンパイラーまたはインタープリターに、ポイントされたオブジェクトの(現在の)値に興味があることを伝えます。以下では、CおよびC ++に焦点を当てます。

ポインターシナリオ

p以下のようなポインタが与えられたCで考えてみましょう...

const char* p = "abc";

...文字「a」、「b」、「c」をエンコードするために使用される数値を含む4バイト、およびテキストデータの終わりを示す0バイトは、メモリのどこかに格納され、その数値アドレスデータはに保存されpます。このように、Cがメモリ内のテキストをエンコードすることをASCIIZといいます。

たとえば、文字列リテラルがたまたまアドレス0x1000にありp、32ビットポインターが0x2000にある場合、メモリの内容は次のようになります。

Memory Address (hex)    Variable name    Contents
1000                                     'a' == 97 (ASCII)
1001                                     'b' == 98
1002                                     'c' == 99
1003                                     0
...
2000-2003               p                1000 hex

アドレス0x1000の変数名/識別子はありませんが、アドレスを格納するポインターを使用して文字列リテラルを間接的に参照できますp

ポインターの逆参照

文字がp指す文字を参照するにはp、次の表記のいずれかを使用して逆参照します(これもCの場合)。

assert(*p == 'a');  // The first character at address p will be 'a'
assert(p[1] == 'b'); // p[1] actually dereferences a pointer created by adding
                     // p and 1 times the size of the things to which p points:
                     // In this case they're char which are 1 byte in C...
assert(*(p + 1) == 'b');  // Another notation for p[1]

ポインタをポイントされたデータを介して移動し、移動中にそれらを逆参照することもできます。

++p;  // Increment p so it's now 0x1001
assert(*p == 'b');  // p == 0x1001 which is where the 'b' is...

書き込むことができるデータがある場合は、次のようなことができます。

int x = 2;
int* p_x = &x;  // Put the address of the x variable into the pointer p_x
*p_x = 4;       // Change the memory at the address in p_x to be 4
assert(x == 4); // Check x is now 4

上記ではx、と呼ばれる変数が必要であることをコンパイル時に知っておく必要があり、コードはコンパイラーにそれを格納する場所を調整するように要求し、アドレスがを介して確実に利用できるようにし&xます。

構造データメンバーの逆参照とアクセス

Cでは、データメンバーを持つ構造体へのポインターである変数がある場合、->逆参照演算子を使用してそれらのメンバーにアクセスできます。

typedef struct X { int i_; double d_; } X;
X x;
X* p = &x;
p->d_ = 3.14159;  // Dereference and access data member x.d_
(*p).d_ *= -1;    // Another equivalent notation for accessing x.d_

マルチバイトデータ型

ポインターを使用するために、コンピュータープログラムは、ポイントされているデータのタイプについての洞察も必要です。そのデータタイプが表すために複数のバイトを必要とする場合、ポインターは通常、データ内の最小番号のバイトをポイントします。

そこで、もう少し複雑な例を見てみましょう。

double sizes[] = { 10.3, 13.4, 11.2, 19.4 };
double* p = sizes;
assert(p[0] == 10.3);  // Knows to look at all the bytes in the first double value
assert(p[1] == 13.4);  // Actually looks at bytes from address p + 1 * sizeof(double)
                       // (sizeof(double) is almost always eight bytes)
++p;                   // Advance p by sizeof(double)
assert(*p == 13.4);    // The double at memory beginning at address p has value 13.4
*(p + 2) = 29.8;       // Change sizes[3] from 19.4 to 29.8
                       // Note earlier ++p and + 2 here => sizes[3]

動的に割り当てられたメモリへのポインター

プログラムが実行され、そこにスローされるデータを確認するまで、必要なメモリ量がわからない場合があります...を使用してメモリを動的に割り当てることができますmalloc。ポインタにアドレスを格納することは一般的な方法です...

int* p = (int*)malloc(sizeof(int)); // Get some memory somewhere...
*p = 10;            // Dereference the pointer to the memory, then write a value in
fn(*p);             // Call a function, passing it the value at address p
(*p) += 3;          // Change the value, adding 3 to it
free(p);            // Release the memory back to the heap allocation library

C ++では、通常、メモリの割り当てはnew演算子を使用して行われ、次のように割り当て解除されますdelete

int* p = new int(10); // Memory for one int with initial value 10
delete p;

p = new int[10];      // Memory for ten ints with unspecified initial value
delete[] p;

p = new int[10]();    // Memory for ten ints that are value initialised (to 0)
delete[] p;

以下のC ++スマートポインターも参照してください。

アドレスの喪失と漏洩

多くの場合、ポインタは、データまたはバッファがメモリ内のどこに存在するかを示す唯一の指標である場合があります。そのデータ/バッファを継続的に使用する必要がある場合、またはメモリを呼び出す、free()またはdeleteメモリのリークを回避する機能が必要な場合、プログラマはポインタのコピーを操作する必要があります...

const char* p = asprintf("name: %s", name);  // Common but non-Standard printf-on-heap

// Replace non-printable characters with underscores....
for (const char* q = p; *q; ++q)
    if (!isprint(*q))
        *q = '_';

printf("%s\n", p); // Only q was modified
free(p);

...または変更の反転を慎重に調整します...

const size_t n = ...;
p += n;
...
p -= n;  // Restore earlier value...
free(p);

C ++スマートポインター

C ++では、スマートポインタオブジェクトを使用してポインタを格納および管理し、スマートポインタのデストラクタが実行されたときに自動的に割り当てを解除することがベストプラクティスです。C ++ 11以降、標準ライブラリは2つを提供していunique_ptrます。

{
    std::unique_ptr<T> p{new T(42, "meaning")};
    call_a_function(p);
    // The function above might throw, so delete here is unreliable, but...
} // p's destructor's guaranteed to run "here", calling delete

...およびshared_ptr共有所有権の場合(参照カウントを使用)...

{
    auto p = std::make_shared<T>(3.14, "pi");
    number_storage1.may_add(p); // Might copy p into its container
    number_storage2.may_add(p); // Might copy p into its container    } // p's destructor will only delete the T if neither may_add copied it

ヌルポインター

CではNULL0さらにC ++ではnullptr、ポインターが変数のメモリアドレスを現在保持していないことを示すために使用でき、逆参照したり、ポインター演算で使用したりしないでください。例えば:

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
int c;
while ((c = getopt(argc, argv, "f:")) != -1)
    switch (c) {
      case f: p_filename = optarg; break;
    }
if (p_filename)  // Only NULL converts to false
    ...   // Only get here if -f flag specified

CおよびC ++では、作り付けの数値型は必ずしもようにデフォルトしていないと同じように0、またboolsfalse、ポインタは常にに設定されていませんNULL。これらはすべて、static変数または(C ++のみ)静的オブジェクトまたはそのベースの直接または間接メンバー変数であるか、ゼロの初期化(たとえばnew T();new T(x, y, z);ポインターを含むTのメンバーでゼロの初期化を実行する)の場合、0 / false / NULLに設定されます。new T;ではない)。

さらに、あなたが割り当てるとき0NULLおよびnullptrポインタへのポインタ内のビットは、必ずしも全てリセットされません:ポインタは、ハードウェアレベルで「0」を含む、またはあなたの仮想アドレス空間内のアドレス0を参照しない場合があります。コンパイラは、それが理由を持っている場合があり、他店のものに許可されていますが、何でもそれはない-あなたが一緒に来て、へのポインタを比較する場合0NULLnullptr予想通りまたはそれらのいずれかを割り当てられていた別のポインタ、比較が働かなければなりません。したがって、コンパイラー・レベルのソース・コードの下では、CおよびC ++言語では「NULL」は少し「魔法の」可能性があります...

メモリアドレスの詳細と、おそらく知る必要がない理由

より厳密には、初期化されたポインタは、NULLまたは(多くの場合仮想)メモリアドレスを識別するビットパターンを格納します。

単純なケースは、これがプロセスの仮想アドレス空間全体への数値オフセットである場合です。より複雑なケースでは、ポインタは特定のメモリ領域に関連している可能性があります。CPUは、CPUの「セグメント」レジスタまたはビットパターンでエンコードされたセグメントIDのいくつかの方法に基づいて選択したり、アドレスを使用したマシンコード命令。

たとえばint*int変数をポイントするように適切に初期化すると、-にキャストした後-変数が存在float*するメモリとはまったく異なる「GPU」メモリ内のメモリにアクセスし、intキャストして関数ポインタとして使用すると、さらにポイントする可能性があります。プログラムのマシンオペコードを保持する個別のメモリ(int*これらのメモリ領域内のランダムで無効なポインタの数値を使用)。

CやC ++などの3GLプログラミング言語は、次のような複雑さを隠す傾向があります。

  • コンパイラーが変数または関数へのポインターを提供する場合、(変数が破壊されたり、割り当てが解除されない限り)自由に逆参照することができます。たとえば、特定のCPUセグメントレジスタを事前に復元する必要があるかどうかなど、コンパイラーの問題です。使用される別個の機械語命令

  • 配列内の要素へのポインターを取得した場合、ポインター演算を使用して、配列内の他の場所に移動したり、要素への他のポインターと比較して正当な配列の1つ前のアドレスを作成したりできます配列内(または、ポインター演算によって同じように最後の1つ前の値に移動されたもの); 再びCとC ++では、これが確実に機能するかどうかはコンパイラ次第です。

  • 共有メモリマッピングなどの特定のOS関数はポインタを提供する場合があり、それらはそれらにとって意味のあるアドレスの範囲内で「正常に動作」します

  • これらの境界を超えて正当なポインターを移動する試み、ポインターに任意の数をキャストする試み、または無関係な型にキャストされるポインターを使用する試みは、通常、未定義の動作をするため、高レベルのライブラリーおよびアプリケーションでは回避する必要がありますが、OS、デバイスドライバーなどのコード。CまたはC ++標準では定義されていない動作に依存する必要があるかもしれませんが、特定の実装またはハードウェアによって定義されています。


であるp[1] *(p + 1) 同じ?つまり、同じ命令p[1] *(p + 1)生成しますか?
Pacerier 2013

2
@Pacerier:N1570ドラフトC標準の6.5.2.1/2から(最初にオンラインで見つけた)「添え字演算子[]の定義は、E1 [E2]が(*((E1)+(E2))と同一であることです。 )」-コンパイラがコンパイルの初期段階でそれらをすぐに同一の表現に変換せず、その後同じ最適化を適用しない理由は想像できませんが、コードが同一であることを誰もが確実に証明できるかわかりませんこれまでに作成されたすべてのコンパイラーを調査する必要はありません。
Tony Delroy、2013

3
@ハニー:値1000の16進数は大きすぎて1バイト(8ビット)のメモリにエンコードできません。1バイトに格納できるのは0から255までの符号なし数値のみです。したがって、アドレス2000だけで1000の16進数を格納することはできません。代わりに、32ビットシステムは32ビット(4バイト)を使用し、アドレスは2000〜2003です。64ビットシステムは64ビットを使用します。ビット-8バイト-2000から2007まで。どちらの方法でも、のベースアドレスpは2000だけです。pそれへの別のポインタがある場合、4または8バイトに2000を格納する必要があります。お役に立てば幸いです。乾杯。
トニーデルロイ2017

1
@TonyDelroy:共用体uに配列が含まれている場合、arrgccとclangはどちらも、左辺値u.arr[i]が他の共用体メンバーと同じストレージにアクセスする可能性があることを認識しますが、左辺値がアクセスする可能性があることは認識し*(u.arr+i)ません。それらのコンパイラの作者が後者がUBを呼び出すと考えるのか、それとも前者がUBを呼び出すと考えるのかはわかりませんが、いずれにせよそれを有効に処理する必要がありますが、2つの式が異なると明確に見なしています。
スーパーキャット2018年

3
C / C ++内でのポインタとその使用についてはほとんど見たことがないので、簡潔かつ簡単に説明しました。
kayleeFrye_onDeck 2018

102

ポインターの逆参照とは、ポインターが指すメモリー位置に保管されている値を取得することを意味します。演算子*はこれを行うために使用され、逆参照演算子と呼ばれます。

int a = 10;
int* ptr = &a;

printf("%d", *ptr); // With *ptr I'm dereferencing the pointer. 
                    // Which means, I am asking the value pointed at by the pointer.
                    // ptr is pointing to the location in memory of the variable a.
                    // In a's location, we have 10. So, dereferencing gives this value.

// Since we have indirect control over a's location, we can modify its content using the pointer. This is an indirect way to access a.

 *ptr = 20;         // Now a's content is no longer 10, and has been modified to 20.

15
ポインターはを指すのではなく、オブジェクトを指します
キース・トンプソン

51
@KeithThompsonポインターはオブジェクトを指すのではなく、オブジェクト(おそらくプリミティブ)が配置されているメモリーアドレスを指します。
mg30rg 2014年

4
@ mg30rg:どんな区別をしているのかわかりません。ポインター値アドレスです。オブジェクトは、定義上、「実行環境におけるデータストレージの領域であり、その内容は値を表すことができます」。「プリミティブ」とはどういう意味ですか?C標準ではこの用語は使用されていません。
キーストンプソン

6
@KeithThompson私がかろうじて指摘していたのは、あなたが実際に答えに価値を加えなかったということです。ポインタ値は確かにアドレスであり、それがメモリアドレスを「指す」方法です。OOP駆動の世界で「オブジェクト」という言葉は誤解を招く可能性があります。「クラスインスタンス」として解釈される可能性があるため(そうです、質問に[C]で[C ++]ではなくラベルが付いていることを知りませんでした)、その言葉を使用しました「copmlex」(構造体やクラスのようなデータ構造)の反対の「プリミティブ」。
mg30rg 2014年

3
この答えに、配列添字演算子[]もポインタを逆参照することを追加します(a[b]はと定義されています*(a + b))。
cmaster-モニカを2014年

20

ポインタは値への「参照」です。ライブラリの呼び出し番号が本への参照によく似ています。呼び出し番号を「逆参照」すると、その本が物理的に通過して取得されます。

int a=4 ;
int *pA = &a ;
printf( "The REFERENCE/call number for the variable `a` is %p\n", pA ) ;

// The * causes pA to DEREFERENCE...  `a` via "callnumber" `pA`.
printf( "%d\n", *pA ) ; // prints 4.. 

本がそこにない場合、司書は叫び始め、図書館を閉鎖します、そして、何人かの人々はそこにない本を見つけようとする人の原因を調査するように設定されています。


17

簡単に言うと、逆参照とは、そのポインタが指している特定のメモリ位置から値にアクセスすることです。


7

ポインタの基本からのコードと説明:

間接参照操作はポインターから始まり、矢印をたどってその指示先にアクセスします。目標は、指示先の状態を確認すること、または指示先の状態を変更することです。ポインターの間接参照操作は、ポインターに指示先がある場合にのみ機能します。指示先を割り当て、ポインターがそれを指すように設定する必要があります。ポインターコードの最も一般的なエラーは、指示先の設定を忘れていることです。コード内のそのエラーが原因で最も一般的なランタイムクラッシュは、失敗した逆参照操作です。Javaでは、不正な逆参照はランタイムシステムによって丁寧にフラグされます。C、C ++、Pascalなどのコンパイルされた言語では、誤った逆参照がクラッシュしたり、微妙なランダムな方法でメモリが破損したりすることがあります。

void main() {   
    int*    x;  // Allocate the pointer x
    x = malloc(sizeof(int));    // Allocate an int pointee,
                            // and set x to point to it
    *x = 42;    // Dereference x to store 42 in its pointee   
}

実際には、xが指すはずの場所にメモリを割り当てる必要があります。あなたの例には未定義の振る舞いがあります。
ペイマン、2011

3

以前の答えはすべて間違っていると思います。逆参照は実際の値にアクセスすることを意味するからです。代わりにウィキペディアは正しい定義を提供します:https : //en.wikipedia.org/wiki/Dereference_operator

ポインター変数を操作し、ポインターアドレスの値と同等のl値を返します。これはポインタの「逆参照」と呼ばれます。

とはいえ、ポインターが指す値にアクセスすることなく、ポインターを逆参照することができます。例えば:

char *p = NULL;
*p;

値にアクセスせずにNULLポインターを逆参照しました。または、次のようにすることもできます。

p1 = &(*p);
sz = sizeof(*p);

この場合も、逆参照しますが、値にはアクセスしません。このようなコードはクラッシュしません。実際に無効なポインタでデータにアクセスすると、クラッシュが発生します。ただし、残念なことに、標準によれば、実際のデータに触れようとしない場合でも、無効なポインターの逆参照は(いくつかの例外を除いて)未定義の動作です。

つまり、ポインタの逆参照とは、逆参照演算子をポインタに適用することを意味します。その演算子は、将来使用するためにl値を返すだけです。


まあ、あなたはセグメンテーションフォールトにつながるだろう、NULLポインタを逆参照しました。
arjun gaur

さらに、「ポインターの逆参照」ではなく、「逆参照演算子」を検索しました。これは、実際には、ポインターによってポイントされているメモリロケーションで値を取得/アクセスすることを意味します。
arjun gaur

やってみました?やった。以下はクラッシュしません: `#include <stdlib.h> int main(){char * p = NULL; * p; 0を返します。} `
stsp

1
@stspコードがクラッシュしないからといって、将来、または他のシステムでコードがクラッシュしないという意味ではありません。

1
*p;未定義の動作を引き起こします。逆参照が値自体にアクセスしないことは正しいですが、コード*p; 値にアクセスします。
MM
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.