web-dev-qa-db-ja.com

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

説明付きの例を含めてください。

453
asir

基本的な用語を確認する

通常は十分です-アセンブリをプログラミングしていない限り-pointer1がプロセスのメモリの2番目のバイト、2が3番目、3が4番目、というように数値メモリアドレスを含む.

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

ポインタが指すメモリ内のデータ/値にアクセスする場合-その数値インデックスを持つアドレスの内容-dereferenceポインター。

異なるコンピューター言語には異なる指示があり、コンパイラーまたはインタープリターに指示された値に興味があることを伝えます。以下では、CとC++に焦点を当てます。

ポインターシナリオ

以下のpなどのポインターを指定して、Cで検討してください...

const char* p = "abc";

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

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

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)
assert(++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 = 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...

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

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

{
    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 共有所有権( 参照カウント )を使用...

{
    std::shared_ptr<T> p(new T(3.14, "pi"));
    number_storage.may_add(p); // Might copy p into its container
} // p's destructor will only delete the T if number_storage didn't copy

ヌルポインター

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

const char* p_filename = NULL; // Or "= 0", or "= nullptr" in C++
char c;
while ((c = getopt(argc, argv, "f:")) != EOF)
    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++では、組み込みの数値型が必ずしもデフォルトで0boolsからfalseにデフォルト設定されるわけではないように、ポインターは常にNULLに設定されるわけではありません。これらはすべて、static変数または(C++のみ)静的オブジェクトまたはそのベースの直接または間接メンバー変数であるか、ゼロ初期化(たとえば、new T();およびnew T(x, y, z);がゼロ初期化を実行する)の場合、0/false/NULLに設定されますポインターを含むTのメンバーでは、new T;はそうではありません)。

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

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

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

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

たとえば、int変数を指すように適切に初期化されたint*は、float*にキャストした後、int変数とはまったく異なる「GPU」メモリの値にアクセスし、関数にキャストするとポインターは、関数のマシンオペコードを保持する個別のメモリを参照する場合があります。

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

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

  • 配列内の要素へのポインターを取得する場合、ポインター演算を使用して配列内の別の場所に移動したり、他の要素へのポインターと比較するのに有効な配列の1つ前のアドレスを作成したりできます配列内(または、ポインター算術演算によって同じ1つ最後の値に同様に移動された)。再びCおよびC++では、この「正しく動作する」ことを保証するのはコンパイラ次第です

  • 特定のOS機能(例:共有メモリマッピングは、ポインタを提供する場合があり、それらは意味のあるアドレスの範囲内で「機能する」

  • 有効なポインターをこれらの境界を越えて移動したり、任意の数値をポインターにキャストしたり、無関係な型にキャストしたポインターを使用しようとすると、通常、 undefined behaviour より高いレベルのライブラリやアプリケーションでは避けるべきですが、OS、デバイスドライバーなどのコードは、CまたはC++によって定義されていない動作に依存する必要がある場合があります。

666
Tony Delroy

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

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.
85
Mahesh

ポインタは値への「参照」です。ライブラリ呼び出し番号が本への参照であるのとよく似ています。電話番号を「参照解除」することは、その本を実際に通過して検索することです。

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.. 

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

15
bobobobo

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

10
Fahad Naeem

からのコードと説明 - ポインタの基礎

間接参照操作はポインタから始まり、その指示先にアクセスするためにその矢印に従います。目的は、指示先の状態を確認すること、または指示先の状態を変更することです。ポインターの間接参照操作は、ポインターに指示先がある場合にのみ機能します。指示先を割り当て、ポインターをそれを指すように設定する必要があります。ポインタコードの最も一般的なエラーは、指示先の設定を忘れていることです。コード内のエラーによる最も一般的なランタイムクラッシュは、失敗した間接参照操作です。 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   
}
7
atp

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

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

とは言っても、ポインタが指す値にアクセスすることなくポインタを間接参照できます。例えば:

char *p = NULL;
*p;

値にアクセスせずにNULLポインタを間接参照しました。または私たちはできる:

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

繰り返しますが、値にはアクセスしません。そのようなコードはクラッシュしないでしょう:クラッシュは実際に access 無効なポインタによるデータへのアクセス時に起こります。ただし、残念ながら、規格によれば、無効なポインタの参照解除は、実際のデータに触れない場合でも、(いくつかの例外を除いて)未定義の動作です。

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

2
stsp