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



What Doesdereferencinga Pointer Mean



解決:

基本的な用語の確認

これは いつもの アセンブリをプログラミングしていない限り、 ポインタ 数値メモリアドレスを含み、1はプロセスのメモリの2番目のバイトを参照し、2は3番目、3は4番目というように続きます。

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

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



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

ポインタシナリオ

Cで考えてみましょう。以下のp..。



const char * p = 'abc';

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

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

メモリアドレス(16進数)変数名内容1000'a '== 97(ASCII)1001'b' == 98 1002'c '== 99 1003 0 ... 2000-2003 p 1000 hex

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



ポインタの間接参照

文字を参照するにはpが指す、間接参照これらの表記法の1つを使用するp(ここでもCの場合):

assert(* p == 'a'); //アドレスpの最初の文字は 'a' assert(p [1] == 'b');になります。 // p [1]は、実際には// pとpが指すものの1倍のサイズを追加することによって作成されたポインタを逆参照します://この場合、それらはCの1バイトであるcharです... assert(* (p + 1)== 'b'); // p [1]の別の表記

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

++ p; // pをインクリメントして0x1001になりますassert(* p == 'b'); // p == 0x1001これは「b」がどこにあるかです...

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

int x = 2; int * p_x =&x; // x変数のアドレスをポインタに入れますp_x * p_x = 4; // p_xのアドレスのメモリを4に変更しますassert(x == 4); // xが4になったことを確認します

上記では、コンパイル時に、という変数が必要になることを知っている必要があります。x、およびコードはコンパイラーにそれを格納する場所を調整するように要求し、アドレスが& NS。

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

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

typedef struct X {int i_;ダブルd_; } NS; X x; X * p =&x; p-> d_ = 3.14159; //データメンバーx.d _(* p).d_ * = -1;を逆参照してアクセスします。 //x.d_にアクセスするための別の同等の表記

マルチバイトデータ型

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

したがって、もう少し複雑な例を見てみましょう。

ダブルサイズ[] = {10.3、13.4、11.2、19.4}; double * p =サイズ; assert(p [0] == 10.3); //最初のdouble値のすべてのバイトを確認することを知っていますassert(p [1] == 13.4); //実際にはアドレスp + 1からのバイトを調べます* sizeof(double)//(sizeof(double)はほとんどの場合8バイトです)++ p; // pをsizeof(double)で進めますassert(* p == 13.4); //アドレスpで始まるメモリのdoubleの値は13.4 *(p + 2)= 29.8; // size [3]を19.4から29.8に変更します//以前の++ pと+2に注意してください=> sizes [3]

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

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

int * p =(int *)malloc(sizeof(int)); //どこかにメモリを取得します... * p = 10; //ポインタをメモリに逆参照してから、fn(* p);に値を書き込みます。 //関数を呼び出し、アドレスp(* p)+ = 3の値を渡します。 //値を変更し、それに3を追加しますfree(p); //メモリを解放してヒープ割り当てライブラリに戻します

C ++では、メモリの割り当ては通常、新しい演算子、および割り当て解除消去:

int * p = new int(10); //初期値が10の1つのintのメモリdeletep; p = new int [10]; //初期値が指定されていない10個のintのメモリdelete [] p; p = new int [10](); //値が(0に)初期化された10個のintのメモリdelete [] p;

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

アドレスの紛失と漏洩

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

const char * p = asprintf( 'name:%s'、name); //一般的ですが非標準のprintf-on-heap //印刷できない文字をアンダースコアに置き換えます.... for(const char * q = p; * q; ++ q)if(!isprint(* q)) * q = '_'; printf( '%s  n'、p); // qのみが変更されましたfree(p);

...または変更の取り消しを慎重に調整します...

const size_t n = ...; p + = n; ... p- = n; //以前の値を復元します... free(p);

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

C ++では、スマートポインターオブジェクトを使用してポインターを格納および管理し、スマートポインターのデストラクタの実行時にポインターの割り当てを自動的に解除することをお勧めします。 C ++ 11以降、標準ライブラリは2つを提供します。割り当てられたオブジェクトの所有者が1人の場合のunique_ptr ...

{std :: unique_ptr p {new T(42、 'meaning')}; call_a_function(p); //上記の関数がスローされる可能性があるため、ここでの削除は信頼できませんが...} // pのデストラクタは「ここ」で実行されることが保証されています。deleteを呼び出します。

...と共有所有権のshared_ptr(参照カウントを使用)..

{auto p = std :: make_shared(3.14、 'pi'); number_storage1.may_add(p); // pをそのコンテナにコピーする可能性がありますnumber_storage2.may_add(p); // pをコンテナにコピーする可能性があります} // pのデストラクタは、may_addがTをコピーしなかった場合にのみTを削除します

ヌルポインタ

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

const char * p_filename = NULL; //または '= 0'、または '= nullptr' in C ++ int c; while((c = getopt(argc、argv、 'f:'))!= -1)switch(c){case f:p_filename = optarg;壊す; } if(p_filename)// NULLのみがfalseに変換されます... //-fフラグが指定されている場合にのみここに取得されます

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

さらに、あなたが割り当てるとき0、NULLおよびポインターへのnullptrポインターのビットは、必ずしもすべてリセットされるわけではありません。ポインターにハードウェアレベルの「0」が含まれていないか、仮想アドレス空間のアドレス0を参照していない可能性があります。コンパイラは、理由がある場合は他の何かをそこに格納できますが、それが何をする場合でも、ポインタを0、ヌル、nullptrまたはそれらのいずれかが割り当てられた別のポインターの場合、比較は期待どおりに機能する必要があります。したがって、コンパイラレベルのソースコードの下では、「NULL」はCおよびC ++言語では「魔法のような」ものになる可能性があります。

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

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

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

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

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

  • コンパイラが変数または関数へのポインタを提供する場合、それを自由に逆参照できます(その間に変数が破棄/割り当て解除されない限り)。特定のCPUセグメントレジスタを事前に復元するか、別のマシンコード命令を使用する必要があります

  • 配列内の要素へのポインタを取得した場合、ポインタ演算を使用して配列内の他の場所に移動したり、要素への他のポインタと比較するのに合法な配列の最後の1つ先のアドレスを形成したりすることもできます。配列内(またはポインター演算によって同じ1つ過去の値に同様に移動されたもの)。繰り返しますが、CおよびC ++では、これが「正しく機能する」ことを確認するのはコンパイラー次第です。

  • 特定のOS機能(例:共有メモリマッピングは、ポインタを提供する可能性があり、それらは、それらにとって意味のあるアドレスの範囲内で「正しく機能する」でしょう。

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


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

int a = 10; int * ptr =&a; printf( '%d'、* ptr); // * ptrを使用して、ポインターを逆参照しています。 //つまり、ポインタが指す値を尋ねています。 // ptrは、変数aのメモリ内の場所を指しています。 // aの場所には、10があります。したがって、逆参照するとこの値が得られます。 //の場所を間接的に制御できるため、ポインタを使用してその内容を変更できます。これは、にアクセスするための間接的な方法です。 * ptr = 20; //これで、のコンテンツは10ではなくなり、20に変更されました。 

ポインタは値への「参照」です。図書館の請求番号が本への参照であるのとよく似ています。呼び出し番号を「逆参照」すると、その本が物理的に処理されて取得されます。

int a = 4; int * pA =&a; printf( '変数 `a`の参照/呼び出し番号は%p  n'、pA); // *により、pAは間接参照されます... `a` via'callnumber'`pA`。 printf( '%d  n'、* pA); // 4を出力します。

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