
a変数に関連付けられたメモリ アドレスを指すポインタb、つまりa変数のメモリ アドレス 1008 を含むポインタb。この図では、コンピューティング アーキテクチャはポインタと非ポインタの両方に同じアドレス空間とデータ プリミティブを使用していますが、必ずしもそうである必要はありません。コンピュータサイエンスにおいて、ポインタは多くのプログラミング言語でメモリ アドレスを格納するオブジェクトです。これは、コンピュータ メモリ内の別の値のアドレスである場合もあれば、場合によってはメモリ マップドハードウェアのアドレスである場合もあります。ポインタはメモリ内の場所を参照し、その場所に格納されている値を取得することをポインタの逆参照といいます。例えるなら、本の索引にあるページ番号は対応するページへのポインタと考えることができます。このようなポインタの逆参照は、指定されたページ番号のページに移動して、そのページにあるテキストを読み取ることで行われます。ポインタ変数の実際の形式と内容は、基盤となるコンピュータ アーキテクチャに依存します。
ポインタを使用すると、反復可能なデータ構造(文字列、ルックアップテーブル、制御テーブル、リンクリスト、ツリー構造など)の走査といった反復的な操作のパフォーマンスが大幅に向上します。特に、ポインタが指すデータをコピーしてアクセスするよりも、ポインタをコピーして逆参照する方が、時間と空間の面で多くの場合はるかに安価です。
ポインタは、手続き型プログラミングにおいて呼び出されるサブルーチンのエントリポイントのアドレスを保持するため、また実行時に動的リンクライブラリ(DLL)へのリンクを行うためにも使用されます。オブジェクト指向プログラミングでは、関数へのポインタはメソッドのバインディングに使用され、多くの場合、仮想メソッドテーブルが使用されます。
ポインタは、より抽象的な参照データ型の、より具体的でシンプルな実装です。いくつかの言語、特に低レベル言語は、何らかのポインタをサポートしていますが、その使用には他の言語よりも多くの制限があります。「ポインタ」は一般的に参照を指すのに使われてきましたが、より正確には、インターフェースがポインタの操作(算術的に)を明示的に許可するデータ構造に適用されます。ポインタ演算)はメモリ アドレスとして扱われますが、マジック クッキーやそのようなことを許可しない機能メモリ アドレスへの保護されたアクセスと保護されていないアクセスの両方を許可するため、特に後者の場合、ポインタの使用にはリスクが伴います。プリミティブ ポインタは、多くの場合整数。ただし、有効なメモリ アドレスではないポインタを逆参照または「ルックアップ」しようとすると、プログラムがクラッシュたり、無効なデータが含まれる可能性があります。この潜在的な問題を軽減するために、型安全性から、ポインタは、基となる表現が整数であっても、ポインタが指すデータの型によってパラメータ化された別の型とみなされます。また、検証や境界チェック対策が講じられる場合もあります。
1955 年、ソビエト連邦のウクライナ人コンピュータ科学者カテリーナ・ユシチェンコは、間接アドレッシングとポインタに類似した最高ランクのアドレスを可能にするAddress プログラミング言語を作成しました。この言語はソビエト連邦のコンピュータで広く使用されました。しかし、ソビエト連邦以外では知られておらず、通常、ポインタの発明は 1964 年にハロルド・ローソンによるものとされています。 [ 2 ] 2000 年、ローソンはIEEEからコンピュータ パイオニア賞を授与されました。「ポインタ変数を発明し、この概念を PL/I に導入し、汎用高水準言語でリンク リストを柔軟に処理する機能を初めて提供した」ためです。[ 3 ]この概念に関する彼の画期的な論文は、1967 年 6 月号の CACM に「PL/I リスト処理」というタイトルで掲載されました。オックスフォード英語辞典によると、ポインタという言葉は、 System Development Corporationの技術メモランダムでスタック ポインタとして初めて印刷物に登場しました。
コンピュータサイエンスにおいて、ポインタは一種の参照である。
データプリミティブ(または単にプリミティブ)とは、1回のメモリアクセスでコンピュータメモリから読み書きできるデータのことです(例えば、バイトとワードはどちらもプリミティブです)。
データ集約(または単に集約)とは、メモリ上で論理的に連続し、1つのデータとしてまとめて扱われるプリミティブのグループです(例えば、集約は論理的に連続する3バイトで構成され、その値が空間内の点の3つの座標を表す場合があります)。集約がすべて同じタイプのプリミティブで構成されている場合、その集約は配列と呼ばれることがあります。ある意味では、マルチバイトワードプリミティブはバイトの配列であり、一部のプログラムではワードをこのように使用します。
これらの定義において、バイトは最小単位であり、各メモリアドレスは異なるバイトを指定します。データの最初のバイトのメモリアドレスは、データ全体のメモリアドレス(またはベースメモリアドレス)とみなされます。
メモリポインタ(または単にポインタ)は、メモリアドレスとして使用されることを目的としたプリミティブです。ポインタはメモリアドレスを指していると言われます。また、ポインタの値がメモリ内のデータのメモリアドレスである場合、ポインタはデータ(メモリ内)を指していると言われます。
より一般的に言えば、ポインタは一種の参照であり、ポインタはメモリ内のどこかに格納されたデータを参照すると言われます。そのデータを取得するには、ポインタを逆参照する必要があります。ポインタを他の種類の参照と区別する特徴は、ポインタの値がメモリ アドレスとして解釈されることを意図している点であり、これはかなり低レベルな概念です。
参照は間接参照の役割を果たします。ポインタの値によって、計算に使用するメモリ アドレス(つまり、どのデータ)が決まります。間接参照はアルゴリズムの基本的な要素であるため、ポインタはプログラミング言語において基本的なデータ型として表現されることがよくあります。静的型付け(または厳密型付け)のプログラミング言語では、ポインタの型によって、ポインタが指すデータの型が決まります。
ポインタは、ほとんどの最新アーキテクチャが提供するアドレス指定機能の上に構築された非常に薄い抽象化です。最も単純な方式では、システム内の各メモリ単位にアドレス、つまり数値インデックスが割り当てられます。メモリ単位は通常、アーキテクチャがバイトアドレス指定可能かワードアドレス指定可能かによって、バイトまたはワードのいずれかになります。これにより、メモリ全体が実質的に非常に大きな配列に変換されます。システムは、指定されたアドレスにあるメモリ単位に格納されている値を取得する操作も提供します(通常はマシンの汎用レジスタを利用します)。
通常の場合、ポインタはシステム内のメモリ単位数よりも多くのアドレスを保持できるほど大きい。このため、プログラムがメモリ単位に対応しないアドレスにアクセスしようとする可能性がある。これは、十分なメモリがインストールされていない(つまり、使用可能なメモリ範囲を超えている)か、アーキテクチャがそのようなアドレスをサポートしていないかのいずれかが原因である。前者のケースは、Intel x86アーキテクチャなどの特定のプラットフォームでは、セグメンテーション違反(segfault)と呼ばれることがある。後者のケースは、ポインタが64ビット長でアドレスが48ビットまでしか拡張されないAMD64の現在の実装で発生する可能性がある。ポインタは特定の規則(正規アドレス)に従う必要があるため、非正規ポインタが逆参照されると、プロセッサは一般的な保護違反を発生させる。
一方、システムによっては、アドレス数よりも多くのメモリユニットを持つものもあります。この場合、メモリの異なる部分を異なるタイミングで使用するために、メモリ分割やページングなどのより複雑な方式が採用されます。x86アーキテクチャの最後のバージョンでは、最大36ビットの物理メモリアドレスがサポートされ、PAEページングメカニズムを介して32ビットの線形アドレス空間にマッピングされていました。そのため、一度にアクセスできるメモリの総量は、可能なメモリの1/16に限られていました。同じコンピュータファミリーの別の例として、 80286プロセッサの16ビット保護モードがあります。これは、16MBの物理メモリしかサポートしていませんでしたが、最大1GBの仮想メモリにアクセスできました。しかし、16ビットのアドレスレジスタとセグメントレジスタの組み合わせにより、1つのデータ構造で64KBを超えるデータにアクセスするのは煩雑でした。
一貫性のあるインターフェースを提供するために、一部のアーキテクチャではメモリマップドI/Oが採用されています。これにより、一部のアドレスはメモリ単位を参照し、他のアドレスはコンピュータ内の他のデバイスのデバイスレジスタを参照することができます。ファイルオフセット、配列インデックス、リモートオブジェクト参照など、他の種類のオブジェクトのアドレスと同様の目的を果たす類似の概念も存在します。
ポインタは、 PL/I、C、C++、Pascal、FreeBASICなどの言語では制限なく直接サポートされており、ほとんどのアセンブリ言語では暗黙的にサポートされています。ポインタは主に参照の構築に使用され、参照はほぼすべてのデータ構造の構築に不可欠であり、プログラムの異なる部分間でデータを渡すためにも使用されます。
リストを多用する関数型プログラミング言語では、データ参照は、 consやそれに対応する要素car と cdrといったプリミティブな構成要素を用いて抽象的に管理されます。これらは、cons セルの最初の要素と 2 番目の要素への特殊なポインタと考えることができます。これが、関数型プログラミング特有の「雰囲気」を生み出しています。このようなcons リストでデータを構造化することで、これらの言語は、データの構築と処理のための再帰的な手段を容易にします。例えば、リストのリストの先頭要素と末尾要素に再帰的にアクセスするなどです。例:「cdr の car を取得する」。対照的に、メモリ アドレスの配列の近似値に基づくポインタの逆参照によるメモリ管理は、変数を命令的にデータを割り当てることができるスロットとして扱うことを容易にします。
配列を扱う場合、重要な検索操作には通常、アドレス計算と呼ばれる段階が含まれます。この段階では、配列内の目的のデータ要素へのポインタを構築します。リンクリストなどの他のデータ構造では、ポインタは参照として使用され、構造のある要素を別の要素に明示的に関連付けます。
ポインタは、パラメータを参照渡しするために使用されます。これは、関数がパラメータに加えた変更を関数の呼び出し元に可視化したい場合に便利です。また、関数から複数の値を返す場合にも役立ちます。
ポインタは、メモリ内の動的変数や配列の割り当てと解放にも使用できます。変数は目的を終えると不要になることが多いため、保持しておくのはメモリの無駄です。したがって、不要になった場合は(元のポインタ参照を使用して)解放するのが良い習慣です。これを怠ると、メモリリーク(多数の冗長なメモリブロックが蓄積され、利用可能な空きメモリが徐々に、あるいは深刻な場合は急速に減少する現象)が発生する可能性があります。
C言語では、ポインタを定義するための基本的な構文は次のとおりです。 [ 4 ]
int * ptr ;ptrこれは、型のオブジェクトへのポインタを格納する変数を宣言しますint。 の代わりに他の型を使用することもできますint。たとえば、bool *ptrは型のオブジェクトへのポインタを宣言しますbool。
ポインタが宣言された後、アドレスを割り当てることができます。C言語では、変数のアドレスは&単項演算子で取得できます。
// int型へのポインタの変数を宣言int * ptr ; // int型の変数を宣言int a = 5 ; // aのアドレスをポインタ変数に代入ptr = &a a ;ポインタを逆参照して、それが指すオブジェクトを取得するには、アスタリスク(* *)を使用できます。
printf ( "%d \n " , * ptr ); // 出力: 5int名前付きポインタの宣言には、次の2つのスタイルがありますptr。
int*ptr;これは、式が*ptr型を持つことを強調しているint。[ 5 ]int*ptr;これは、ptr型があることを強調していますint*。このスタイルは、C よりもC++でより一般的です。 [ 6 ] [ 7 ]とはどちらもへのポインタであると宣言していますが、は であると宣言しています。これは、が直後の変数にのみ適用されるためです。[ 5 ]int*x,y;int*x,y;xintyint*
アスタリスクは、代入式の左側で使用することで、a同じスコープ内になくてもオブジェクトを変更することができます。
* ptr = 8 ;後でアクセスされた場合a、その新しい値は8になります。
C言語では自動記憶域期間のオブジェクトの暗黙的な初期化が指定されていないため、[ 8 ]ポインタ変数が予期しない場所を指すことがあり、未定義の動作を引き起こすことがあります。これに対処するため、ポインタはヌルポインタ値で初期化されることがあり、Cではマクロで表されますNULL。[ 9 ] C23以降では、nullptr代替として型であるも使用できますnullptr_t。[ 10 ]
int * ptr = NULL ; int * ptr = nullptr ; // C23以降ヌルポインタの逆参照は未定義の動作を引き起こし、[ 11 ]予期しないバグや結果につながる可能性があります。
メモリを直接調べると、この例がより明確になるかもしれません。 はaメモリのアドレス 0x8130 に、 はptr0x8134 に配置されていると仮定します。また、これは 32 ビット マシンであり、int 型は 32 ビット幅であると仮定します。次のコード スニペットを実行した後、メモリには次の内容が格納されます。
int a = 5 ; int * ptr = NULL ;(ここに示されているヌルポインタは0x00000000です。)アドレスをに割り当てて:aptr
ptr = &a a ;以下のようなメモリ値が得られます。
次に、ptrコーディングによる逆参照によって:
* ptr = 8 ;コンピュータは(0x8130である)の内容を取得しptr、そのアドレスを「特定」し、その場所に8を割り当てることで、以下のメモリ領域が得られます。
明らかに、アクセスするとa値 8 が得られます。これは、前の命令がaポインタを介しての内容を変更したためですptr。
リスト、キュー、ツリーなどのデータ構造を設定する際には、構造の実装方法や制御方法を管理するためにポインタが必要になります。ポインタの代表的な例としては、開始ポインタ、終了ポインタ、スタックポインタなどがあります。これらのポインタは、絶対ポインタ(実際の物理アドレスまたは仮想メモリ内の仮想アドレス)または相対ポインタ(絶対開始アドレス(「ベース」)からのオフセット。通常、完全なアドレスよりも少ないビット数で表現できますが、解決には通常1つの追加の算術演算が必要になります)のいずれかです。
相対アドレスは手動メモリ分割の一種であり、その利点と欠点の多くを共有しています。16ビットの符号なし整数を含む2バイトのオフセットを使用して、最大64 KiB(2¹⁶バイト)のデータ構造に対して相対アドレス指定を行うことができます。アドレスがハーフワード、ワード、またはダブルワード境界にアラインメントされるように強制すれば、これを128、256、または512 KiBに簡単に拡張できます(ただし、オフセットをベースアドレスに加算する前に、1、2、または3ビットの「左シフト」ビット演算を追加して、オフセットを2、4、または8倍に調整する必要があります)。しかし、一般的に、このような方式は手間がかかるため、プログラマの利便性を考慮して、絶対アドレス(およびその基盤となるフラットなアドレス空間)が好まれます。
文字の16進ASCII値(例:X'29')のような1バイトのオフセットを使用して、配列内の別の整数値(またはインデックス)(例:X'01')を指すことができます。このようにして、ルックアップテーブルを使用することなく、文字を「生データ」から使用可能なシーケンシャルインデックス、そして絶対アドレスへと非常に効率的に変換できます。
C言語では、配列のインデックス付けはポインタ演算の観点から正式に定義されています。つまり、言語仕様では、がa[i]と等価であることが要求されます[ 12 ]。なぜなら、が配列の名前である場合、ほとんどの場合、式 はの最初の要素へのポインタに評価されるため[ 13 ] 、配列にアクセスするための構文は、ポインタの逆参照に使用できる構文と同一だからです。たとえば、配列は次のように宣言して使用できます。*(a + i)aaaa
int a [ 5 ]; // 5 つの連続する整数を宣言しますint * ptr = a ; // 式内の配列名は、ほとんどの場合ポインタに評価されますptr [ 0 ] = 1 ; // ポインタは配列構文でインデックス付けできます* ( a + 1 ) = 2 ; // 配列はポインタ構文でインデックス付けできます* ( 1 + a ) = 2 ; // ポインタの加算は可換です2 [ a ] = 4 ; // 添え字演算子は可換です (おそらく珍しい)これは 5 つの整数からなるブロックを割り当て、そのブロックに という名前を付けますa。この式は、ほとんどの場合、ブロックの最初の要素へのポインタとして評価されます。ポインタのもう 1 つの一般的な用途は、 mallocaから動的に割り当てられたメモリを指すことです。malloc は、配列として使用できる、要求されたサイズ以上の連続したメモリ ブロックを返します。
ほとんどの演算子のオペランドとして配列を使用すると、配列は配列の最初の要素へのポインタとして評価されますが、sizeof演算子の場合はそうではありません。この例では、は(配列のサイズ)sizeof(a)に評価されますが、はポインタ自体のサイズであるに評価されます。5 * sizeof(int)sizeof(ptr)sizeof(int *)
配列のデフォルト値は次のように宣言できます。
int a [ 5 ] = { 2 , 4 , 3 , 1 , 5 };32ビットリトルエンディアンマシンで、アドレス0x1000aから始まるメモリに が配置されている場合、メモリには次の内容が含まれます(値はアドレスと同様に16進数です)。
ここに示されているのは、2、4、3、1、5 の 5 つの整数です。これらの 5 つの整数はそれぞれ 32 ビット (4 バイト) を占め、最下位バイトが最初に格納されます (これはリトルエンディアンのCPU アーキテクチャです)。アドレス0x1000から連続して格納されます。
ポインタを用いたC言語の構文は以下のとおりです。
a0x1000を意味します。a + 10x1004を意味します。「+ 1」は、1 のサイズ、つまり 4 バイトを追加することを意味します。int*aは の内容を逆参照することを意味しますa。内容をメモリ アドレス ( 0x1000 ) とみなして、その場所 ( 0x0002 )の値を検索します。a[i]iは、0 から始まる要素番号を意味し、aそのうち は に変換されます*(a + i)。最後の例は、 の内容にアクセスする方法ですa。詳しく見ていきましょう。
a + iiは、の 番目の要素のメモリ位置でありa、 から始まりますi = 0。*(a + i)そのメモリアドレスを取得し、それを逆参照して値にアクセスします。以下は、 C言語における連結リストの定義例です。
struct LinkedList { void * data ; // このリンクのデータstruct LinkedList * next ; // 次のリンク。存在しない場合は NULL };このポインタ再帰の定義は、基本的にHaskell言語の参照再帰の定義と同じです。
data LinkedList a = Nil | Cons a ( LinkedList a )はNil空のリストであり、は型のconsCons a (LinkedList a)セルで、 型の別のリンクも 型です。aa
しかし、参照を用いた定義は型チェックが行われ、混乱を招く可能性のあるシグナル値は使用されません。そのため、C言語のデータ構造は通常、ラッパー関数を介して扱われ、その正確性は厳密にチェックされます。
ポインタを使用すると、変数をアドレスで渡すことができ、その値を変更することが可能になります。たとえば、次のC言語のコードを考えてみましょう。
// int n のコピーは、呼び出し元のコードに影響を与えることなく、関数内で変更できます。void passByValue ( int n ) { n = 12 ; }// 代わりにポインタ m が渡されます。m が指す値のコピーは作成されません。void passByAddress ( int * m ) { * m = 14 ; }int main ( void ) { int x = 3 ;// x の値のコピーを引数として渡すpassByValue ( x ); // 関数内で値は変更されたが、ここから先は x は 3 のままである// xのアドレスを引数として渡すpassByAddress ( & x ); // xは実際には関数によって変更され、ここでは14になっていますreturn 0 ; }プログラムによっては、必要なメモリ量がユーザーの入力内容によって変化する場合があります。このような場合、プログラマはメモリを動的に割り当てる必要があります。これは、通常変数が格納されるスタックではなく、ヒープにメモリを割り当てることで実現されます(ただし、変数はCPUレジスタに格納することもできます)。動的メモリ割り当てはポインタを介してのみ行うことができ、一般的な変数と同様に、名前を指定することはできません。
ポインタは、動的に割り当てられたメモリブロックのアドレスを格納および管理するために使用されます。このようなブロックは、データオブジェクトやオブジェクトの配列を格納するために使用されます。ほとんどの構造化言語およびオブジェクト指向言語は、ヒープまたはフリーストアと呼ばれるメモリ領域を提供し、そこからオブジェクトが動的に割り当てられます。
以下のC言語のサンプルコードは、構造体オブジェクトが動的に割り当てられ、参照される方法を示しています。標準Cライブラリには、ヒープからメモリブロックを割り当てる関数が用意されていますmalloc()。この関数は、割り当てるオブジェクトのサイズをパラメータとして受け取り、オブジェクトの格納に適した新しく割り当てられたメモリブロックへのポインタを返します。割り当てに失敗した場合は、ヌルポインタを返します。
// 部品在庫アイテムtypedef struct { int id ; // 部品番号char * name ; // 部品名float cost ; // コスト} Item ;// 新しい Item オブジェクトを割り当てて初期化しますItem * makeItem ( const char * name ) { Item * item ;// 新しい Item オブジェクト用のメモリブロックを割り当てますitem = malloc ( sizeof ( * item )); if ( ! item ) { return NULL ; }// 新しい Item のメンバーを初期化しますmemset ( item , 0 , sizeof ( * item )); item -> id = -1 ; item -> name = NULL ; item -> cost = 0.0 ;// 新しいアイテムに名前のコピーを保存するitem -> name = malloc ( strlen ( name ) + 1 ); if ( ! item -> name ) { free ( item ); return NULL ; } strcpy ( item -> name , name );// 新しく作成された Item オブジェクトを返すreturn item ; }以下のコードは、メモリオブジェクトが動的に解放される方法、つまりヒープまたはフリーストアに戻される方法を示しています。標準Cライブラリには、free()以前に割り当てられたメモリブロックを解放し、ヒープに戻すための関数が用意されています。
// Item オブジェクトを解放するvoid destroyItem ( Item * item ) { // null オブジェクト ポインタがないかチェックするif ( ! item ) { return ; }// アイテム内に保存されている名前文字列を解放するif ( item -> name ) { free ( item -> name ); item -> name = NULL ; }// Item オブジェクト自体を解放するfree ( item ); }一部のコンピューティングアーキテクチャでは、ポインタを使用してメモリまたはメモリマップドデバイスを直接操作できます。
ポインタにアドレスを割り当てることは、マイクロコントローラをプログラミングする際に非常に役立つツールです。以下は、int型のポインタを宣言し、16進数のアドレス(この例では定数0x7FFF )で初期化する簡単な例です。
int * hardware_address = ( int * ) 0x7FFF ;1980年代半ば、BIOSを使用してPCのビデオ機能にアクセスするのは遅かった。ディスプレイを多用するアプリケーションは通常、16進定数0xB8000を80個の符号なし16ビット整数値の配列へのポインタにキャストすることで、 CGAビデオメモリに直接アクセスしていた。各値は、下位バイトにASCIIコード、上位バイトに色で構成されていた。したがって、青地に明るい白で5行目2列目に文字「A」を配置するには、次のようなコードを記述した。
#define VID ((unsigned short (*)[80])0xB8000)void foo ( void ) { VID [ 4 ] [ 1 ] = 0x1F00 | 'A' ; }プログラムの流れを制御するために使用される制御テーブルは、通常、ポインタを多用します。ポインタは通常、テーブルエントリに埋め込まれており、たとえば、同じテーブルエントリで定義された特定の条件に基づいて実行されるサブルーチンのエントリポイントを保持するために使用されます。ただし、ポインタは、実際のアドレスの配列、またはアドレス自体(使用可能なプログラミング言語の構造によって異なります)を含む、他の独立した、しかし関連付けられたテーブルへの単なるインデックスである場合もあります。また、ポインタは、以前のテーブルエントリを指し示す(ループ処理の場合など)ため、または一部のテーブルエントリをスキップするために前方を指す(スイッチやループからの「早期」終了の場合など)ためにも使用できます。後者の場合、「ポインタ」は単にテーブルエントリ番号自体であり、簡単な算術演算によって実際のアドレスに変換できます。
多くのプログラミング言語では、ポインタには、それが指すオブジェクトが特定の型を持つという追加の制約があります。たとえば、ポインタが整数を指すように宣言されている場合、言語はプログラマが浮動小数点数などの整数ではないオブジェクトを指すことを防ぎ、エラーの一部を排除しようとします。
例えば、次のC言語コードでは:
int * money ; char * bags ;moneyは整数ポインタであり、bagsは文字ポインタです。
GCCでは、以下のコードを実行すると「互換性のないポインタ型からの代入」というコンパイラ警告が表示されます。
バッグ=お金;なぜならmoney、とがbags異なる型で宣言されていたからです。
コンパイラの警告を抑制するには、型キャストによって代入を明示的に行う必要があります。
bags = ( char * ) money ;これは、の整数ポインタをmoney文字ポインタにキャストしてに代入することを意味しますbags。
C標準の2005年の草案では、ある型から派生したポインタを別の型のポインタにキャストする場合、両方の型のアライメントの正しさを維持する必要があると規定されている(6.3.2.3 ポインタ、7項):[ 14 ]
char * external_buffer = "abcdef" ; int * internal_data ;internal_data = ( int * ) external_buffer ; // 「結果として得られるポインタが正しくアラインされていない場合」は未定義の動作になりますポインタ演算が可能な言語では、ポインタの演算は型のサイズを考慮します。たとえば、ポインタに整数を加算すると、その整数に型のサイズの倍を掛けたアドレスを指す別のポインタが生成されます。これにより、上記の C 配列の例で示したように、特定の型の配列の要素のアドレスを簡単に計算できます。ある型のポインタを異なるサイズの別の型にキャストすると、ポインタ演算の計算方法が異なることをプログラマは想定しておく必要があります。たとえば、C では、money配列が 0x2000 から始まりsizeof(int)4 バイトであるのに対しsizeof(char)、 が 1 バイトの場合、 は0x2004money + 1を指しますが、 は0x2001を指します。キャストのその他のリスクとしては、「ワイド」なデータが「ナロー」な場所 (たとえば ) に書き込まれる際のデータ損失、ビットシフトによる値の予期しない結果、特に符号付き値と符号なし値の比較における問題などがあります。bags + 1bags[0] = 65537;
一般的に、コンパイル時にどの型キャストが安全かを判断することは不可能ですが、一部の言語では実行時型情報を格納し、危険な型キャストが実行時に有効であることを確認するために使用できます。他の言語では、安全な型キャストの保守的な近似値のみを受け入れるか、あるいは全く受け入れません。
C および C++ では、2 つのポインタが等しいと比較されたとしても、それらが同等であるとは限りません。これらの言語とLLVMでは、このルールは「2 つのポインタが同じアドレスを指しているからといって、それらが互換的に使用できるという意味で等しいとは限らない」と解釈され、ポインタ間の違いはそれらの由来と呼ばれます。[ 15 ]整数型へのキャストはuintptr_t実装依存であり、それによって得られる比較は、2 つのポインタが互換可能かどうかについてのさらなる洞察を提供しません。さらに、バイトへの変換と算術演算は、ポインタの使用を追跡しようとするオプティマイザを混乱させる可能性があり、この問題は学術研究でまだ解明されていません。[ 16 ]
ポインタは、プログラムが定義されていない可能性のあるオブジェクトにアクセスしようとすることを可能にするため、さまざまなプログラミングエラーの原因となる可能性があります。しかし、ポインタの有用性は非常に高く、ポインタなしではプログラミングタスクを実行することが困難になる場合があります。そのため、多くの言語では、ポインタの便利な機能の一部を、ポインタの落とし穴(ポインタハザードとも呼ばれる)の一部なしで提供するように設計された構造が作成されています。この文脈では、メモリを直接アドレス指定するポインタ(この記事で使用されるもの)は、生ポインタまたは裸ポインタ[ 17 ]は、スマートポインタとは対照的である。
ポインタの大きな問題点の1つは、ポインタが数値として直接操作できる限り、未使用のアドレスや他の用途で使用されているデータを指すようにできてしまうことです。多くの言語、特にほとんどの関数型プログラミング言語や、Javaのような最近の命令型プログラミング言語では、ポインタをより不透明なタイプの参照(通常は単に参照と呼ばれる)に置き換えています。参照はオブジェクトを参照するためにのみ使用でき、数値として操作することはできないため、このようなエラーを防ぐことができます。配列のインデックス付けは特別なケースとして扱われます。
アドレスが割り当てられていないポインタは、ワイルドポインタと呼ばれます。このような初期化されていないポインタを使用しようとすると、初期値が有効なアドレスでない場合や、使用によってプログラムの他の部分が破損する可能性があるため、予期しない動作を引き起こす可能性があります。その結果、多くの場合、セグメンテーション違反、記憶域違反、またはワイルドブランチ(関数ポインタや分岐アドレスとして使用された場合)が発生します。
明示的なメモリ割り当てを行うシステムでは、ポインタが指すメモリ領域を解放することで、ダングリングポインタが発生する可能性があります。この種のポインタは危険かつ巧妙です。なぜなら、解放されたメモリ領域には解放前と同じデータが含まれている可能性がありますが、その後、以前のコードには認識されない無関係なコードによって再割り当てされ、上書きされる可能性があるからです。ガベージコレクションを備えた言語では、スコープ内に参照がなくなると自動的に解放が実行されるため、この種のエラーを防ぐことができます。
Javaのようにポインタやポインタ演算を抽象化する言語では、イテレータを使用できます。イテレータは、ポインタを直接使用せずにメモリ領域やコレクションを反復処理する、より安全な手段と言えます。C++、C#、Rustなど、ポインタを持つ言語であっても、コレクション用のイテレータを備えているものが多くあります。
import java.util.Iterator ; import java.util.List ;List <String> fruits = List.of ( " Apple" , " Banana" , " Cherry" , " Date " ) ; Iterator <String> iterator = fruits.iterator ( ) ;//イテレータを使用してリストを走査しますwhile ( iterator.hasNext ( ) ) { //次の要素を取得しますString fruit = iterator.next ( ) ; System.out.println ( fruit ); }C++では、イテレータはオーバーロードすることができoperator++、これはC言語におけるポインタのインクリメントの従来の構文と似ています。カスタム定義のイテレータは、従来、次の3つの演算子をオーバーロードします。
operator==/ operator!=(等価比較)operator++(インクリメント)operator*(逆参照)import std ;std :: vectorを使用します。// vector などのコレクションは、begin() メソッドと end() メソッドを持つイテレータ型を定義します。 vector < int > v { 1 , 2 , 3 , 4 , 5 }; // 'it' は vector<int>::iterator 型です。// ポインタ演算と同様にコレクションを反復処理します。for ( auto it = v . begin (); it != v . end (); ++ it ) { std :: print ( "{}" , * it ); }C++などの一部の言語はスマートポインタをサポートしています。スマートポインタは、参照として機能するだけでなく、単純な形式の参照カウントを使用して動的メモリの割り当てを追跡します。オブジェクトが一連のスマートポインタを介して間接的に自身を参照する参照サイクルが存在しない場合、スマートポインタはダングリングポインタやメモリリークの可能性を排除します。Delphiの文字列は参照カウントをネイティブにサポートしています。
Rustプログラミング言語は、ガベージコレクションに頼ることなくポインタのバグを排除するために、借用チェッカー、ポインタのライフタイム、およびヌルポインタのオプション型に基づく最適化を導入しています。
ヌルポインタには、ポインタが有効なオブジェクトを参照していないことを示すために予約された値があります。ヌルポインタは、長さが不明なリストの末尾や、何らかのアクションの実行失敗などの状態を表すためによく使用されます。このヌルポインタの使用方法は、 null許容型やオプション型のNothing値と比較できます。
ダングリングポインタとは、有効なオブジェクトを指していないポインタのことで、プログラムがクラッシュしたり、予期しない動作をしたりする可能性があります。PascalやC言語では、明示的に初期化されていないポインタは、メモリ内の予測不可能なアドレスを指すことがあります。
以下のサンプルコードは、ダングリングポインタの例を示しています。
int func ( void ) { char * p1 = malloc ( sizeof ( char )); // ヒープ上のどこかの(未定義の)値char * p2 ; // ダングリング(未初期化)ポインタ* p1 = 'a' ; // malloc() が NULL を返していない限り、これは問題ありません。* p2 = 'b' ; // これは未定義の動作を引き起こします}ここでは、p2メモリ内の任意の場所を指す可能性があるため、代入を実行すると、*p2 = 'b';メモリの未知の領域が破損したり、セグメンテーション違反が発生したりする可能性があります。
ポインタがプログラムのエントリポイントのアドレス、または何も返さない関数の開始アドレスとして使用され、かつ初期化されていないか破損している場合、そのアドレスに対して呼び出しやジャンプが行われると、「ワイルドブランチ」が発生したと言われます。言い換えれば、ワイルドブランチとは、ワイルド(ダングリング)な関数ポインタのことです。
その結果は通常予測不可能であり、ポインタが「有効な」アドレスであるかどうか、またそのアドレスに(偶然にも)有効な命令(オペコード)が存在するかどうかによって、エラーはさまざまな形で現れる可能性があります。ワイルドブランチの検出は、証拠の多くが既に事前に、あるいは分岐位置での1つ以上の不適切な命令の実行によって破壊されている可能性があるため、最も困難で厄介なデバッグ作業の1つとなる場合があります。命令セットシミュレータが利用可能であれば、ワイルドブランチが実行される前に検出できるだけでなく、その履歴の完全または部分的なトレースも提供できます。
自己相対ポインタとは、その値がポインタ自身のアドレスからのオフセットとして解釈されるポインタです。したがって、データ構造に、データ構造自体の一部を指す自己相対ポインタメンバがある場合、データ構造は自己相対ポインタの値を更新することなくメモリ内で再配置できます。[ 18 ]
引用された特許でも、自己相対ポインタという用語が同じ意味で使用されています。しかし、この用語の意味は他の方法でも使用されています。
ベースポインタとは、別のポインタの値からのオフセット値を持つポインタのことです。これは、ブロックの開始アドレスをベースポインタに割り当てることで、データのブロックを格納およびロードするために使用できます。[ 20 ]
一部の言語では、ポインタが別のポインタを参照できるため、元の値を取得するには複数の逆参照操作が必要になります。間接参照のレベルが増えるごとにパフォーマンスコストが増加する可能性がありますが、複雑なデータ構造で正しい動作を実現するためには、間接参照が必要になる場合があります。たとえば、C言語では、リンクリストを、リストの次の要素へのポインタを含む要素で定義するのが一般的です。
typedef struct LinkedList { int value ; struct LinkedList * next ; } LinkedList ;LinkedList * head = NULL ;この実装では、リスト全体の代わりとしてリストの最初の要素へのポインタを使用します。リストの先頭に新しい値が追加された場合、ポインタをhead新しい要素を指すように変更する必要があります。C言語の引数は常に値渡しされるため、二重間接参照を使用することで挿入を正しく実装でき、リストの先頭への挿入を処理するための特別なコードが不要になるという望ましい副次的効果が得られます。
// *head にソートされたリストがある場合、それより前のすべての要素の値以下である最初の場所に要素 item を挿入します。void insert ( LinkedList ** head , LinkedList * item ) { // p は要素へのポインタを指しますLinkedList ** p = head ; while ( * p && ( * p ) -> value < item -> value ) { p = & ( * p ) -> next ; } item -> next = * p ; * p = item ; }// 呼び出し元は次のようにします: insert ( & head , item );この場合、の値がのitem値より小さい場合head、呼び出し元のはhead新しいアイテムのアドレスに適切に更新されます。
基本的な例として、C (および C++) の main 関数のargv引数があります。プロトタイプでは(または) と指定されています。これは、変数自体が文字列の配列 (配列の配列) へのポインタであるため、 は0 番目の文字列 (慣例としてプログラム名) へのポインタであり、 は0 番目の文字列の 0 番目の文字です。char **argvchar *argv[]argv*argv**argv
一部のプログラミング言語では、ポインタは実行可能コードを参照できます。つまり、関数、メソッド、またはプロシージャを指すことができます。関数ポインタは、呼び出される関数のアドレスを格納します。この機能は関数を動的に呼び出すために使用できますが、ウイルスやその他の悪意のあるソフトウェア作成者が好んで使う手法でもあります。
// 2つの整数パラメータを受け取り、整数値を返す関数int sum ( int n1 , int n2 ) { return n1 + n2 ; }int main ( void ) { int a = 3 ; int b = 5 ; // 関数への関数ポインタ (int, int) -> int // 関数 sum を指すint ( * fp )( int , int ) = & sum ; int x = ( * fp )( a , b ); // 引数 a と b で関数 sum を呼び出すint y = sum ( a , b ); // 引数 a と b で関数 sum を呼び出す}C++では、関数ポインタの代わりにラムダ(匿名関数)を使用できます。
import std ;using Callback = void ( * )( int );void process ( int a , int b , Callback cb ) { callback ( a + b ); }void printResult ( int result ) { std :: println ( "結果: {}" , result ); }int main ( int argc , char * argv []) { // 従来の関数ポインタを使用: process ( 1 , 2 , printResult ); // 出力: "Result: 3"// ラムダ式を使用: process ( 3 , 4 , []( int result ) -> void { std :: println ( "Result: {}" ); }); // 出力: "Result: 7" }二重リンクリストやツリー構造では、要素に保持されたバックポインタは、現在の要素を参照する項目を「指し返します」。これらはナビゲーションや操作に役立ちますが、メモリ使用量が多くなります。
Ada は、すべてのポインタに型が付けられ、安全な型変換のみが許可される、強力な型付け言語です。すべてのポインタはデフォルトで に初期化されnull、nullポインタを介してデータにアクセスしようとすると例外が発生します。Ada のポインタはアクセス型と呼ばれます。Ada 83 ではアクセス型に対する算術演算は許可されていませんでしたが (多くのコンパイラ ベンダーが非標準機能として提供していました)、Ada 95 ではパッケージ を介してアクセス型に対する「安全な」算術演算がサポートされていますSystem.Storage_Elements。
Windowsプラットフォーム向けのBASICの古いバージョンにはSTRPTR()、文字列のアドレスを返す関数と、VARPTR()変数のアドレスを返す関数がいくつかサポートされていました。Visual BasicOBJPTR() 5では、オブジェクトインターフェイスのアドレスを返す関数と、関数のアドレスを返すADDRESSOF演算子もサポートされていました。これらの型はすべて整数ですが、その値はポインタ型が保持する値と同等です。
ただし、FreeBASICやBlitzMaxなどの新しいBASIC方言では、ポインタの実装が網羅されています。FreeBASICでは、ANYポインタ( C言語の` { { _void *
dim as integer f = 257 dim as any ptr g = @ f dim as integer ptr i = g assert ( * i = 257 ) assert ( ( g + 4 ) = ( @ f + 1 ) )C言語では、ポインタはアドレスを格納する変数であり、nullになることもあります。各ポインタは指す先となる型を持ちますが、ポインタ型間では自由に型変換が可能です。ただし、関数ポインタとオブジェクトポインタの間では型変換はできません。「voidポインタ」と呼ばれる特殊なポインタ型は、任意の(関数以外の)オブジェクトを指すことができますが、直接逆参照できないという制限があります。アドレス自体は、ポインタを十分な大きさの整数型にキャストすることで直接操作できる場合が多いですが、結果は未定義です。以前のC標準では十分な大きさであることが保証された整数型はありませんでしたが、C99uintptr_tではで定義された型が指定されています。<stdint.h>ただし、実装側でこの型を提供する必要はありません。
ポインタ演算、つまり算術演算(および大きさの比較)でポインタのターゲット アドレスを変更する機能は、言語標準によって単一の配列オブジェクトの範囲内、またはその直後に限定されており、そうでない場合は未定義の動作を引き起こします。ポインタに加算または減算すると、ポインタはそのデータ型のサイズの倍数だけ移動します。たとえば、4 バイトの整数値へのポインタに 1 を加えると、ポインタが指すバイト アドレスが 4 だけ増加します。これにより、ポインタが連続する整数配列の次の要素を指すように増加します。これは多くの場合、意図した結果です。void型voidにはサイズがないため、ポインタが指すアドレスに加算できないため、ポインタ演算はポインタに対して実行できません。ただし、gccや他のコンパイラは、非標準の拡張として、それを であるかのように扱い、バイト演算を実行します。void *char *
ポインタ演算は、プログラマにさまざまな型を扱うための単一の方法を提供します。それは、実際のバイト単位のオフセットではなく、必要な要素数を加算または減算することです。特に、C 標準では、配列のna[n]番目の要素である構文 は、が指す要素の内容である と同等であると明示的に宣言されています。これは、たとえば、と の両方が配列の 4 番目の要素にアクセスすることを意味します。a*(a + n)a + na[3]3[a]a
ポインタ演算は強力ではあるものの、コンピュータのバグの原因となる可能性があります。初心者プログラマーを混乱させ、異なる文脈で処理させてしまう傾向があります。式は通常の算術式である場合もあれば、ポインタ演算式である場合もあるため、両者を混同しやすいのです。こうした問題に対処するため、多くの現代的な高級プログラミング言語では、アドレスを用いたメモリへの直接アクセスは許可されていません。
ポインタ、または は、ANSI C で汎用ポインタ型としてサポートされています。 へのポインタはvoid、任意のオブジェクト (関数を除く)のアドレスを格納でき、は代入時に暗黙的に他のオブジェクトポインタ型に変換されますが、逆参照する場合は明示的にキャストする必要があります。ANSI C より前は、K&R は「型に依存しないポインタ」の目的でを使用していました。void *voidchar *
int x = 4 ; void * p1 = & x ; int * p2 = p1 ; // void * は暗黙的に int * に変換されますint a = * p2 ; int b = * ( int * ) p1 ; // インラインで逆参照する場合、暗黙的な変換はありませんC++ はC のポインタと型キャストを完全にサポートしています。また、コンパイル時に意図しない危険なキャストを検出するのに役立つ新しい型キャスト演算子群もサポートしています。さらに、C++ はポインタとは全く異なる別の形式の参照もサポートしており、これは単に参照または参照型と呼ばれます。
C++11以降、C++標準ライブラリはスマートポインタも提供しており、状況によってはプリミティブなCポインタよりも安全な代替手段として使用できます。
std::unique_ptr(リソースの固有所有権を表し、コピーはできませんが移動は可能です)std::shared_ptr(リソースの共有所有権を表し、リソースは最後の共有ポインタが破棄またはリセットされたときにのみ削除されます。)std::weak_ptr(所有権を持たず、参照カウントをインクリメントせずに共有ポインタのリソースを指すことができる)std::hazard_pointer(単一書き込み・複数読み取り型のポインタ。一度に所有できるスレッドは最大で1つ)std::unique_ptrこれは、C++11 で非推奨となり、C++17 で削除された以前のスマートポインタstd::auto_ptrに代わるものです。
C言語とは対照的に、C++では、void*代入時であっても、ポインタ型から他のポインタ型への暗黙的な型変換は許可されていません。これは、不注意による、あるいは意図しない型変換を避けるための設計上の決定ですが、ほとんどのコンパイラは、他の型変換に遭遇した場合、エラーではなく警告のみを出力します。
int x = 4 ; void * p1 = & x ; int * p2 = p1 ; // これは失敗します: void* からの暗黙的な型変換はありませんint * p3 = reinterpret_cast < int *> ( p1 ); // C++ キャストvoid&C++ では、 (void へのポインタ)を補完する(void への参照)は存在しませんvoid*。なぜなら、参照はそれが指す変数のエイリアスのように動作し、型が の変数は決して存在しないからですvoid。
C++ では、クラスの非静的メンバーへのポインタを定義できます。クラスにMyClassメンバーがある場合T a、は型の&MyClass::aメンバーへのポインタになります。このメンバーはオブジェクトまたは関数になります。[ 22 ]これらは、演算子と演算子の右辺で使用して、対応するメンバーにアクセスできます。aT MyClass::*.*->*
struct Integer { int value ; [[ nodiscard ]] int get () const noexcept { return value ; } };Integer s1 ; // デフォルト構築、s1.value = 0 Integer * ps = & s1 ;int Integer ::* ptr = & Integer :: value ; // Integer::value へのポインタint ( Integer ::* fp )() const = & Integer :: get ; // Integer::get へのポインタs1 . * ptr = 1 ; std :: println ( "{}" , ( s1 . * fp )()); // 1 と出力ps ->* ptr = 2 ; std :: println ( "{}" , ( ps ->* fp )()); // 2 と出力以下は、メンバへのポインタを含む宣言です。
クラスX { // ... };class Y { // ... };char X ::* a ; // char へのメンバへのポインタchar X ::* b [ 5 ]; // char へのメンバへのポインタの配列char * X ::* c ; // char へのポインタへのメンバへのポインタchar X ::** d ; // char へのメンバへのポインタへのポインタchar ( * e )[ 5 ]; // char の配列へのメンバへのポインタchar X ::* f (); // char へのメンバへのポインタを返す関数char Y ::* X ::* g ; // char へのポインタへのメンバへのポインタへのポインタchar X ::* X ::* h ; // char へのポインタへのメンバへのポインタへのポインタchar ( X ::* i ())[ 5 ]; // 文字配列へのメンバへのポインタを返す関数char ( X ::* j )() // 文字を返すメンバ関数へのポインタ char ( X :: * k [ 5 ])(); // 文字を返すメンバ関数へのポインタの配列と()は[]よりも優先順位が高い*。[ 23 ]
C# プログラミング言語では、ポインタは、ポインタを含むコードブロックをキーワードでマークするunsafeか、System.Runtime.CompilerServicesポインタアクセス用の名前空間を介してサポートされます。構文は基本的に C++ と同じで、指すアドレスはマネージドメモリでもアンマネージドメモリでも構いません。ただし、マネージド メモリへのポインタ (マネージド オブジェクトへのポインタ) は、キーワードを使用して宣言する必要があります。これにより、ポインタがスコープ内にある間、ガベージ コレクタがfixedメモリ管理の一環として指すオブジェクトを移動することが防止され、ポインタ アドレスが有効に保たれます。
ただし、例外として、IntPtr構造体を使用する場合は、メモリ管理された同等の構造体であり、キーワードもアセンブリもint*必要ありません。この型は、のメソッドを使用する場合によく返されます。たとえば、次のようになります。unsafeCompilerServicesSystem.Runtime.InteropServices
using System ; using System.Runtime.InteropServices ;// プロセスのアンマネージド メモリから 16 バイトのメモリを取得しますIntPtr pointer = Marshal.AllocHGlobal ( 16 ) ;// 割り当てられたメモリを使って何かを行う// 割り当てられたメモリを解放するMarshal.FreeHGlobal ( pointer ) ;.NETフレームワークSystemには、および名前空間に多くのクラスとメソッドSystem.Runtime.InteropServices(クラスなどMarshal) が含まれており、これらは .NET 型 (たとえば、 ) を多くのアンマネージド型およびポインタ (たとえば、または)System.Stringに変換し、アンマネージド コードとの通信を可能にします。これらのメソッドのほとんどは、メモリ内の任意の場所に影響を与える可能性があるため、アンマネージド コードと同じセキュリティ権限要件を持ちます。LPWSTRvoid*
C# では、スタックに割り当てられた配列を安全なコードで使用できますSystem.Span。[ 24 ]
名前空間Wikipedia.Examples ;Systemを使用します。public class Example { static void Main ( string [] args ) { int num = 1024 ; unsafe { // バイトポインタを作成して int をバイトに変換しますbyte * p = ( byte * ) & number ; Console . Write ( "整数の 4 バイトは次のとおりです: " ); for ( int i = 0 ; i < sizeof ( int ); ++ i ) { Console . Write ( " {0:X2}" , * p ); ++ p ; } Console . WriteLine (); }// スタックに割り当てられた配列は、ポインタまたは Span<T> を介して行うことができます。 unsafe { int * numbers = stackalloc int [ 5 ]; } Span < int > numbers = stackalloc int [ 5 ]; } }C# は、C や C++ と同様にvoid*(void ポインタ) 型を持っていますが、使用は非常に推奨されません。[ 25 ]
C言語やC++言語とは異なり、右揃えのアスタリスクはコンパイラで受け入れられません。例:
int * a , b ; int * a , * b ; // C# では不正ですC および C++ では、これは と を宣言しますaがint*、bC int# では、これは両方の変数を として宣言しますint*。[ 25 ]
COBOLプログラミング言語は、変数へのポインタをサポートしています。LINKAGE SECTIONプログラムの<string>タグ内で宣言されたプリミティブまたはグループ(レコード)データオブジェクトは、本質的にポインタベースであり、プログラム内に割り当てられるメモリは、データ項目のアドレス(通常は単一のメモリワード)の領域のみです。プログラムのソースコードでは、これらのデータ項目は他のWORKING-STORAGE変数と同様に使用されますが、その内容はポインタを介して間接的に暗黙的にアクセスされますLINKAGE。
指し示す各データオブジェクトのメモリ領域は、通常、外部ステートメントを使用するか、またはステートメントなどの組み込み拡張言語構造を介して動的に割り当てられます。CALLEXEC CICSEXEC SQL
COBOLの拡張バージョンでは、USAGEISPOINTER句を使用して宣言されたポインタ変数も提供されます。このようなポインタ変数の値は、SETおよびSETADDRESS文を使用して設定および変更されます。
COBOLの拡張版の中には、実行可能コードのアドレスPROCEDURE-POINTERを格納できる変数を提供するものもあります。
PL /I言語は、すべてのデータ型 (構造体へのポインタを含む) へのポインタ、再帰、マルチタスク、文字列処理、および豊富な組み込み関数を完全にサポートしています。PL/I は、当時のプログラミング言語と比べて大きな進歩でした。PL /I ポインタは型付けされていないため、ポインタの逆参照や代入にキャストは必要ありません。ポインタの宣言構文は で、これは "xxx" という名前のポインタを宣言します。ポインタは変数とともに使用されます。ベース変数は、デフォルトロケータ ( )または なし ( )で宣言できます。ここで、xxx はベース変数であり、要素変数、構造体、または配列である可能性があり、ppp はデフォルトポインタです。このような変数は、明示的なポインタ参照なしで ( ) 参照することも、デフォルトロケータ (ppp) または他の任意のポインタ ( ) への明示的な参照を使用して参照することもできます。DECLARE xxx POINTER;BASEDDECLARE xxx BASED(ppp);DECLARE xxx BASED;xxx=1;qqq->xxx=1;
ポインタ演算はPL/I標準に明示的に含まれているわけではありませんが、UNSPEC組み込み関数と擬似変数によってポインタ演算が可能になり、多くのコンパイラでは の形式の式が使用できますptr = ptr±expression。IBM PL/Iにも、この演算を実行する組み込み関数がありますPTRADD。ポインタ演算は常にバイト単位で行われます。
IBM Enterprise PL/I コンパイラには、 と呼ばれる新しい形式の型付きポインタがありますHANDLE。
Dプログラミング言語はC と C++ の派生言語であり、C ポインタと C 型キャストを完全にサポートしています。ポインタ演算をサポートし、ポインタスライスも備えています。D のポインタはvoid*暗黙的に に変化することができます。[ 26 ]
D言語では、参照に を使用することが可能ですref。これは自動的に逆参照し、ポインタ演算を禁止し、関数のエスケープ(つまり を介してreturn)を許可しません。
ポインタスライスは、長さを持つポインタと考えることができます。
import std.stdio ;void main () { int [] arr = [ 10 , 20 , 30 , 40 , 50 ]; int * ptr = & arr [ 0 ]; int [] slice = ptr [ 0 .. 3 ]; // 配列をインデックス 0 から 3 まで (0 以外) スライスしますwriteln ( "スライス: " , slice ); // 出力: スライス: [10, 20, 30] }Eiffelオブジェクト指向言語は、ポインタ演算を用いず、値と参照のセマンティクスを採用しています。しかしながら、ポインタクラスは提供されており、ポインタ演算、型変換、明示的なメモリ管理、Eiffel以外のソフトウェアとのインターフェース、その他の機能を提供します。
Fortran-90 では、厳密に型付けされたポインタ機能が導入されました。Fortran のポインタは、単純なメモリ アドレスだけでなく、配列次元の下限と上限、ストライド (たとえば、任意の配列セクションをサポートするため)、その他のメタデータもカプセル化します。関連付け演算子,は、属性を持つ変数に=>ポインタを関連付けるために使用されます。Fortran-90 のステートメントは、ポインタをメモリ ブロックに関連付けるためにも使用できます。たとえば、次のコードは、リンク リスト構造を定義して作成するために使用できます。POINTERTARGETALLOCATE
type real_list_t real :: sample_data ( 100 ) type ( real_list_t ), pointer :: next => null () end type型( real_list_t )、ターゲット:: my_real_list型( real_list_t )、ポインタ:: real_list_tempreal_list_temp => my_real_list do read ( 1 , iostat = ioerr ) real_list_temp % sample_data if ( ioerr /= 0 ) exit allocate ( real_list_temp % next ) real_list_temp => real_list_temp % next end doFortran-2003では、プロシージャポインタのサポートが追加されました。また、C言語との相互運用性機能の一環として、CスタイルのポインタをFortranポインタに変換したり、その逆を行うための組み込み関数もサポートされています。
Go にはポインタがあり、C/C++ のポインタと同様に宣言されますが、先頭にアスタリスクが付きます。たとえば、へのポインタはとT記述されます*T。C とは異なり、Go にはガベージ コレクションがあり、ポインタ演算は許可されていません。C++ のような参照型は存在しません。マップやチャネルなどの一部の組み込み型はボックス化されており (つまり、内部的には可変構造体へのポインタです)、make関数を使用して初期化されます。ポインタと非ポインタ間の統一された構文へのアプローチとして、矢印 ( ->) 演算子は削除されました。ポインタのドット演算子は、逆参照されたオブジェクトのフィールドまたはメソッドを参照します。ただし、これは 1 レベルの間接参照でのみ機能します。
Javaにはポインタの明示的な表現はありません。代わりに、オブジェクトや配列などのより複雑なデータ構造は参照を使用して実装されます。言語には明示的なポインタ操作演算子はありません。ただし、コードがヌル参照(ヌルポインタ)を逆参照しようとすると、java.lang.NullPointerExceptionがスローされます。参照されていないメモリオブジェクトが占める領域は、実行時にガベージコレクションによって自動的に解放されます。 [ 27 ]
Java は、それぞれ弱参照とファントム参照を実装するクラスjava.lang.ref.WeakReferenceとを提供します。java.lang.ref.PhantomReference
ポインタの実装はPascalとほぼ同じで、VARプロシージャ呼び出しのパラメータも同様です。Modula -2はPascalよりもさらに厳密な型付けがされており、型システムから逸脱する方法は少なくなっています。Modula-2の派生言語( Modula-3など)の中には、ガベージコレクション機能を持つものもあります。
Modula-2と同様に、ポインタが利用可能です。型システムを回避する方法は依然として少ないため、Oberonとその派生言語は、ポインタに関してModula-2やその派生言語よりも安全です。Modula -3と同様に、ガベージコレクションは言語仕様の一部です。
ポインタを特徴とする多くの言語とは異なり、標準ISO Pascal では、ポインタは匿名の動的に作成された変数のみを参照でき、標準の静的変数やローカル変数を参照することはできません。[ 28 ]ポインタ演算はありません。ポインタには関連付けられた型も必要であり、ある型へのポインタは別の型へのポインタと互換性がありません (たとえば、char へのポインタは integer へのポインタと互換性がありません)。これにより、他のポインタ実装、特にPL/IやCで使用される実装に内在する型のセキュリティの問題が解消されます。また、ダングリング ポインタによって引き起こされるリスクも一部解消されますが、標準の手順 ( Cにあるライブラリ関数disposeと同じ効果があります)を使用して参照領域を動的に解放できるため、ダングリング ポインタのリスクは完全には解消されていません。[ 29 ]free
しかし、Free Pascal、[ 30 ] Turbo Pascal 、 Embarcadero DelphiのObject Pascalなど、一部の商用およびオープンソースの Pascal (またはその派生) コンパイラ実装では、ポインタは標準の静的変数またはローカル変数を参照することが許可されており、あるポインタ型から別のポインタ型にキャストできます。さらに、ポインタ演算は制限されていません。ポインタに加算または減算すると、どちらの方向にもそのバイト数だけ移動しますが、標準のプロシージャを使用すると、ポインタは宣言されたデータ型のサイズだけ移動します。型指定のないポインタも という名前で提供されており、他のポインタ型と互換性があります。IncDecPointer
ポインタは、逆参照演算子を使用して宣言されます^。変数の定義で使用される場合、ポインタは型への参照であり、その型は組み込み型(言語コンパイラによって定義される型)か、別のユニット、現在のユニット、またはメインプログラムで明示的に宣言された型のいずれかです。
ポインタ宣言は次のようになります(Pascalは大文字と小文字を区別しないため、型と変数には大文字と小文字のどちらでも使用できます)。
タイプ月= ( 1 月, 2 月, 3 月, 4 月, 5 月, 6 月, 7月, 8 月, 9 月, 10 月, 11 月, 12 月) ;calptr = ^ Calendar_Type ;カレンダータイプ=レコード前へ、次へ:CalPtr ;月:月数;日:バイト;年:整数;イベント:文字列;終わり;変数それは: ^整数;NextCalendar 、カレンダー: ^ calptr ;始める新しい(カレンダー)calendar ^. prev := nil ;calendar ^. next := nil ;カレンダー^.月:= 9 月;カレンダー^。日:= 11 ;カレンダー^.年:= 2001 ;新しい(それ);^ : = 1 ;新しい( nextCalendar ) ;nextCalendar ^. prev := Calendar ;nextCalendar ^. Next := NIL ;新規(カレンダー)...処分する(それ)破棄(カレンダー);Dispose ( nextCalendar ) ;終わり;2 行目では新しい型monthsを作成し、その年の月を表す名前付き定数を作成します。3 行目ではレコード型へのポインタを定義します。4 ~ 10 行目ではそのレコードを定義します。12 ~ 15 行目ではポインタ変数を定義します。13 行目ではスカラーへのポインタを定義し、14 ~ 15 行目ではリンク リストを作成します。18 行目ではnew関数によってメモリを割り当て、そのメモリのアドレスを変数calendarに格納します。19 ~ 23 行目ではリンク リストを初期化します。26 行目ではポインタthatが指すメモリへの代入を示します。28 ~ 31 行目ではリンク リストに新しいリンクを作成します。33 ~ 35 行目ではこれらのポインタに割り当てられたメモリを解放します。
Perlプログラミング言語は、pack 関数と unpack 関数という形でポインタをサポートしていますが、使用されることは稀です。これらは、コンパイルされた OS ライブラリとの単純なやり取りのみを目的としています。その他のすべての場合、Perl は参照を使用します。参照は型付けされており、ポインタ演算は一切許可されません。これらは複雑なデータ構造を構築するために使用されます。[ 31 ]
Rust は参照を使用します。これはポインタに似ていますが、有効な値を指すことが保証されています。生ポインタの操作はあまり一般的ではなく、一般的には低レベルのシステム プログラミングで使用されます。生ポインタはunsafeブロック内でのみ逆参照できます。[ 32 ] [ 33 ] [ 34 ]生ポインタの操作はにありますstd::ptr。
Rust には null ポインタ定数はありません。代わりに で与えられますstd::ptr::null()。
Rustには、いくつかの種類のスマートポインタも存在します。
std::boxed::Box: C++のstd::unique_ptr[ 35 ]に相当std::rc::Rc参照カウント方式のシングルスレッド共有ポインタに相当std::sync::Arc: アトミックに参照カウントされるスレッドセーフな共有ポインタに相当std::rc::Weak: C++のstd::weak_ptr[ 36 ]に相当Rustでは、ポインタはまたは とT表記されます。以下は、Rustにおける生ポインタの例です。*const T*mut T
fn main () { let mut num : i32 = 42 ;let r1 = & num as * const i32 ; let r2 = & mut num as * mut i32 ;unsafe { println! ( "r1 は {} を指しています" , * r1 ); println! ( "r2 は {} を指しています" , * r2 );* r2 = 100 ; println! ( "num は現在: {}" , num ); } }ハロルド・ローソンのポインタ。
*子の動作は未定義です。単項演算子によるポインタの逆参照に無効な値としては、*ヌルポインタなどがあります。{{cite book}}: CS1 maint: 複数名: 著者リスト (リンク) CS1 maint: 数値名: 著者リスト (リンク)委員会草案。