ポインタ (プログラミング)とは? わかりやすく解説

Weblio 辞書 > 辞書・百科事典 > 百科事典 > ポインタ (プログラミング)の意味・解説 

ポインタ (プログラミング)

出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2025/06/03 22:54 UTC 版)

ポインタあるいはポインター(pointer)は、 コンピュータで処理するデータプログラム場所記憶して指示するレジスタ変数の分類や呼称 [1]

概要

コンピュータは処理対象のデータと処理内容であるプログラムとを、一定の規則で記憶装置に並べて処理を行う。 記憶装置の特定の場所を指示すため、(現在のコンピュータの多くは)連続した整数のアドレス(Address)、番地を1バイト単位で割当てている。またプロセッサの内部にもレジスタと呼ばれる記憶場所を、数個から数10個程度配置している。レジスタや記憶装置の中身は全て有限桁ビット列のデジタル数値であるが、特にアドレス指定のための数値を記憶するレジスタや、記憶装置での特定アドレス(高水準言語での特定変数)を、ポインタと呼んで区別している [1]

命令セットでのポインタ

プログラムはInstruction(命令・指示)と呼ばれる、個々のプロセッサ(CPU)に対応した命令を、処理順序に従って並べることで実現されている(命令の集まりのことは命令セットと呼ばれる)。 並べた命令を自動的に実行するためには、プログラムカウンタ(Program Counter、PC)と呼ばれる、CPUに内蔵された記憶回路(レジスタ)が用いられる。 プログラムカウンタには、現在実行中(あるいは次に実行予定)の命令のアドレス[2]が記憶され、CPUで現在の命令が処理されるとともに、プログラムカウンタには次に実行予定の命令の所在地(アドレス)が収納される。 プログラムによっては、条件分岐やループといった処理も必要になる。 これらはジャンプ等の命令を実行することで、間接的にプログラムカウンタの値をジャンプ先のアドレスに書換えることで実施される。プログラムカウンタは命令ポインタ(Instruction Pointer、IP)と呼ぶ場合もあり、場所を指示するポインタの1つである。

高級言語が利用されプログラムが大規模で複雑になると、プログラムを分割して作成管理利用するようになる。 分割されたプログラムは、言語によってサブルーチン(Subroutine)やプロシージャ(Procedure)、関数(Function)やメソッド(Method)などと呼ばれる。 これらの実現には、引数の受渡し、戻り値の受渡し、呼出し先へのジャンプ、呼出し元へのジャンプなどが必要になるが、多くの場合スタックと呼ばれるLIFO(Last In, First Out)データ構造を利用して実現している。スタックの先頭を示すレジスタが用意される場合もあり、Stack Pointer(SP)などと呼ばれ、これもポインタの1つである。

データ構造でのポインタ

コンピュータで扱うデータ形式は有限桁の整数浮動小数点数を基本とし、それらを規格や処理内容に従い、適切にエンコード(符号化)して記憶装置に配置する必要がある。 多く用いられるソート整列や探索文字列処理や数値計算などは、コンピュータの命令セットプログラミング言語の種類に依存しない、一般化された解法アルゴリズムとして類型化されている。 また各種アルゴリズムに適したデータ構造も類型化されている [3]

例えば学生のデータを作成し処理する場合、学生毎に記憶する内容は氏名住所生年月日、各授業の出欠状況や試験結果など多岐にわたる。 こうした学生1人分のデータは、構造体クラスを用いて実現される。 複数の学生データの全体について、氏名の辞書順やある試験結果の順に並べるソート処理や、ある年に生まれた学生や及第点の学生の存在有無を調べる探索処理、転入した学生の追加処理などを考慮してデータ構造が設計採用される。 データ構造の類型として、配列や構造体に加えて連結リストが挙げられる。 連結リストは、学生の氏名や住所などデータそのものに加えて、別の学生への連結(リンク)を1つ以上持つデータ構造であり、データの追加削除や整列に適している [3] [4]

連結リストは、LISPJavaC++では言語仕様やクラスライブラリで用意されている。一方でC言語ではポインタをメンバーとする構造体として、利用者が設計し実現する必要がある。 連結リストにより制限をかけた利用方法が後入先出のスタックと先入先出のキューである。 また上下(親子)関係を明確にしたものが木構造である。 グラフ構造は連結リストをより一般化したものである [3] [4] [5]

下記は前後用にリンクを2つ持つリストのための、構造体の例である。

struct student {
    int      serialnumber;                         /* 学籍番号 */
    char   familyname[MAXNAME];       /* 氏名の氏 */
    char   personalname[MAXNAME];  /* 氏名の名 */
    int      birthyear;                                /* 誕生年 */
    :
    :
    struct student *prev;                    /* 前の学生 */
    struct student *next;                    /* 次の学生 */
};

プログラミング言語C』では上記のような構造体を、自己参照的構造体として紹介し、木構造が具体例で用いられている [1] [3] [4] [5]

C言語のポインタ

最も典型的なポインタの例としては、C言語におけるポインタが挙げられる。C言語のポインタは「特定のメモリ領域を指し示す」ものである。ポインタを経由してメモリ上のデータにアクセスする際、参照するデータの型に応じたポインタ型を用いる。たとえば、int型のデータ[6]にアクセスする場合は、int*型を用いる。int*型を持つ変数を「intへのポインタ」(pointer to int[7]) と呼ぶ。

C言語にポインタが存在する理由は、効率上の問題である。C言語は、元々UNIXを記述するシステム用言語として開発されたものである。したがって、アセンブリ言語で実行できる操作のほぼ全てを行える必要があった。そのため、特定のメモリ領域への値の直接代入能力を持つなど、他のプログラミング言語と比較すると異色とも言える強力なポインタ機能を備えている。

C言語の実行モデルでは、実行プログラム上の関数コード、データが全て1次元のアドレスに直列配置される。そのため、データはおろか、関数のアドレスを取得し、他の関数にエントリーポイント情報として渡すこともできる。

また、C言語の関数では、引数は、値渡しだけをサポートし、参照渡しをサポートしない。これは、アドレスの数値を取得すれば、参照に可能な全てを行えるため、実質的に参照を数値と同一視できるからである。実際、初期のC言語では、アドレス値は、整数型互換するものとして扱われていた。これは、値と参照を明確に区別するPascalなどとは対照的である。現在[いつ?]でもC言語は、void*により任意のメモリ領域にアクセスできる。なお後発のC++では参照渡しもサポートするようになった。

しかし、コード領域も含むメモリを直接扱えるということは、言語レベルでは(意図的でないとしても)不正なメモリアクセスを事実上保護できないということを示しており、C言語のプログラムにおけるポインタ関連のバグの多さがそれを証明している。

実際の例

一般的なC言語のソースコードでは、ポインタが指している領域の値を参照する間接演算子 (indirection operator) "*"と、アドレス演算子 (address operator) "&"を用いて記述される。未初期化のポインタ変数は、不定の領域を指している。しかし、その場合、「未初期化状態」と「有効な領域を指している状態」の区別がつかない。そのため、Null(ヌル)値を代入することによって、ポインタが無効な領域を指していることを明示する必要がある。

単純なポインタ

  • 宣言例
/* int型変数 n を宣言 */
int n;
/* intへのポインタ型変数 ptr を宣言 */
int *ptr;
/* int型変数 n のアドレスをポインタに代入 */
ptr = &n;

アスタリスク*前後のスペースの有無は任意である。

上記の2つの変数nptrは、以下のようにまとめて宣言することもできるが、混乱を招く恐れがある[8]

/* intへのポインタ型変数 ptr と、int型変数 n を宣言 */
int *ptr, n;

複数のポインタ変数をまとめて宣言する場合は、以下のように書く必要がある。

int *ptr0, *ptr1;

C言語の処理系では通例、無効なポインタを示す値として下記のようなNULLマクロが定義されている。

#define NULL ((void*)0)

C言語では、voidへのポインタは任意の型へのポインタに自由に代入することができる。ポインタに無効値を代入する場合、通例このNULLマクロを使う。

int *ptr = NULL;

一方、C++では、NULLは整数定数のゼロに等しい。

#define NULL 0

そのため、C++では下記のように書くこともできる。

int *ptr = 0;

C++ではNULLが整数定数のゼロに等しいことから起こる関数オーバーロードのルックアップに関する問題を解決するため、C++11以降では、std::nullptr_t型として評価されるキーワードnullptrが定義された。C++11以降でもNULLは引き続き利用可能だが、整数定数のゼロに等しいとは限らず、実装は処理系依存となる[9]

int *ptr = nullptr;
  • 利用例

下記はポインタptrの参照先である変数nに整数値10を代入することになる。

int n = 0;
int *ptr = &n;
*ptr = 10;
printf("%d\n", n); /* 10 */
  • 配列とポインタ

C言語における配列(固定長配列)とポインタはそれぞれ異なるデータ型であるが、配列の添え字演算子は、ポインタの加減算とデリファレンスの糖衣構文である。

double a[10];
int i;
for (i = 0; i < 10; ++i) {
    const double x = i * 0.1;
    /* 以下はともに同じ意味を持つ。 */
#if 1
    a[i] = x;
    printf("%f\n", a[i]);
#else
    *(a + i) = x;
    printf("%f\n", *(a + i));
#endif
}

実行時に要素数の決まる配列を作成する際など、動的にメモリ領域を確保するときは結果をポインタで受け取る。確保したメモリを解放するときもポインタを利用する。メモリ解放直後のポインタは無効な領域を指しており、これを「ダングリングポインタ」(dangling pointer) と呼ぶ。ダングリングポインタが指している領域を誤って使用することのないように、セキュリティ対策として明示的にNULLを代入しておく手法が推奨されている[10]

int i;
int *ptr = malloc(sizeof(int) * 10);
for (i = 0; i < 10; ++i) {
  ptr[i] = i;
}
free(ptr); /* 解放により ptr はダングリングポインタとなる。 */
ptr = NULL;
  • 関数の引数

C言語の関数は前述のように参照渡しをサポートせず、値渡しのみをサポートするため、出力は戻り値(返り値)による1つのみを持つことしかできないが、ポインタを利用することで疑似的に複数の出力を持つ関数を定義することが可能となる。

/* xはdouble型配列へのポインタであり、const double *xと宣言することもできる。 */
double func(const double x[], int num, double *minVal, double *maxVal) {
  int i;
  double sum = 0.0;
  assert(num > 0);
  *minVal = +DBL_MAX;
  *maxVal = -DBL_MAX;
  for (i = 0; i < num; ++i) {
    *minVal = MIN(x[i], *minVal);
    *maxVal = MAX(x[i], *maxVal);
    sum += x[i];
  }
  return sum;
}
/* 関数の呼び出し例。 */
double x[] = { 3, 19, 1, -3, -8, 0, 4 };
double minVal, maxVal, sum;
sum = func(x, 7, &minVal, &maxVal);

下記は標準入力を整数値に変換し、scanf関数の第2引数の参照先であるnにその整数値を出力する例である。scanf関数は書式文字列に応じて、可変長引数に渡された実引数の型が何であるかを判断する。例えば%d書式はintへのポインタが渡されたとみなす。

int n;
int *ptr = &n;
scanf("%d", ptr);

または

int n;
scanf("%d", &n);
  • ポインタへのポインタ

「ポインタへのポインタ」(多重間接参照、ダブルポインタ)を定義することも可能である。動的に確保したメモリへのポインタを関数引数で返却するときや、ポインタ配列を扱うときなどに利用される。

int *ptr;
int **pptr;
pptr = &ptr;
  • ポインタ配列の例
#include <stdio.h>
/* 
argc : コマンドライン引数の数。
argv : コマンドライン引数の文字列群。ゼロ終端文字列先頭要素へのポインタ char* の配列があり、その先頭要素へのポインタであり、char *argv[] と宣言することもできる。配列の要素数は argc + 1 個。
最後の要素 argv[argc] は常に NULL となる(番兵)。
https://ja.cppreference.com/w/cpp/language/main_function
*/
int main(int argc, char **argv) {
  while (*argv != NULL) printf("%s\n", *argv++);
}

ポインタ配列先頭要素へのポインタargvを操作して、それぞれの要素が指すゼロ終端文字列char*)を標準出力に書き出している。しかし、間接演算子"*"とインクリメント演算子"++"のどちらの優先度が高いのかを知らないと、このような記述を理解することはできない。したがって、保守作業の際にバグを誘発しやすいため、以下のように記述したほうがよいとする主張もある。

while (*argv != NULL) {
  printf("%s\n", *argv);
  argv++;
}

関数ポインタ

上述の通り、C言語では関数を指すポインタ (pointer to function / function pointer) を作成することができる。

ポインタ演算

記憶域 (メモリ) のアドレス空間は、1次元空間である。たとえば32ビットシステムのアドレス空間は、16進数表記で 0x00000000 から 0xFFFFFFFF (4GiB-1) までの整数値が有効な範囲である。ポインタはこのアドレス空間を抽象化し、メモリ上の任意位置のデータ(オブジェクト)にアクセスするためのデータ型である。なお、プロセスのアドレス空間は物理メモリ上のアドレス(物理アドレス)に直接対応するとは限らない。通例、オペレーティングシステムによって物理メモリは抽象化され、プロセスごとに仮想アドレス空間が割り当てられ、プロセスごとの仮想アドレス空間におけるメモリの読み書き処理はOSによって物理アドレスに対する処理に変換される(メモリマッピング)。

ポインタに対する算術演算は、加減算のみが許可される。ポインタに加減算すると、そのポインタが指すデータ型のサイズに比例したオフセットがアドレスに加減算されることになる。これは配列の添え字演算子が、ポインタの加減算とデリファレンスの糖衣構文であることからも自明である。つまり、ある型Tへのポインタの加減算は、メモリ全体をTの配列とみなして、インデックスを増減していることに他ならない。

int* p = NULL;
printf("Size of int = %d\n", (int)sizeof(int));
printf("%p\n", p);
p++; /* sizeof(int) * 1 だけアドレスが加算される。 */
printf("%p\n", p);
p += 10; /* sizeof(int) * 10 だけアドレスが加算される。 */
printf("%p\n", p);
p--; /* sizeof(int) * 1 だけアドレスが減算される。 */
printf("%p\n", p);

バイト単位のアドレッシングが必要な場合、char / signed char / unsigned charへのポインタを利用する。これらの型はサイズが1であることが規格で保証されているため、これらの型へのポインタに対する加減算操作はバイト単位のアドレッシングとなる。

なお、汎用ポインタvoid*は型が不明のため、アドレスの加減算を行なうことができないが、GCC拡張の非標準動作ではvoid型のサイズに1を割り当てるため、加減算が可能となっている[11][12]

void* pv = NULL;
pv++; /* C/C++標準規格に準拠した環境ではコンパイルエラーとなる。 */
printf("%p\n", pv);

ポインタ型のサイズは処理系依存であり、通例ターゲットとするプロセッサアーキテクチャのレジスタ幅と同じサイズを持つ。

オフセット計算などでポインタ間の差を得る必要がある場合、<stddef.h>で定義されているptrdiff_t型を用いる。ポインタ(アドレス値)を整数型に代入する必要がある場合、<stdint.h>で定義されているポインタ互換の整数型intptr_tuintptr_tを用いる(C99およびC++11で標準化されている)。なお、C/C++ではintlongなどの組み込み整数型のサイズもまた処理系依存である。移植性を損なうため、ポインタを扱う目的で組み込み整数型を直接使用してはならない。

// C99 の例。
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
...
int a[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int* p1 = &a[0]; // または (a + 0)
int* p2 = &a[9]; // または (a + 9)
printf("p1 = %p, p2 = %p\n", p1, p2);
ptrdiff_t difference = p2 - p1;
printf("Difference as pointer = %td\n", difference); // 9
intptr_t i1 = (intptr_t)p1;
intptr_t i2 = (intptr_t)p2;
printf("Difference in bytes = %jd\n", (intmax_t)(i2 - i1)); // sizeof(int) * 9

問題点

ポインタには不正な領域を示しうるという問題がある。たとえば、 近年[いつ?]セキュリティ上で問題となっているバッファオーバーランの原因の多くは、ポインタ演算のエラーで起こる不正領域の書き換えによるものである。また、「オブジェクトそのものに対する操作」と「オブジェクトの位置に対する操作」が混在することは、プログラマの混乱を招きやすい。このような問題もあって、JavaC#など、C言語よりも新しい後発のプログラミング言語では、言語レベルでのポインタ機能は、排除されるか制限される方向にある。

しかし、プログラマーに直接ポインタ操作を許可していない言語でも、ポインタ概念は存在する。たとえば、配列中にオブジェクトを格納し、それを要素のインデックスで参照すれば、これは「ポインタ概念」を活用していることになる。したがって、配列の要素数を超えた領域をアクセスすれば、エラーが発生する。しかし、配列へのインデックスアクセスを完全に排除してしまうと、その言語の制限が厳しくなり、単純な動作を簡易に記述できる領域を狭めてしまう(言語の表現力が低下する)。このように、ポインタには危険性があるが、プログラミングをするうえでは、非常に強力なテクニックである。また、C言語でマイコンの周辺デバイスを制御する場合、メモリバス上の特定のアドレスにあるレジスタに値を読み書きする必要があるため、必須のテクニックとなる。

一方、関数型言語などの発展により、ポインタの必要性は、今後減少する可能性が 考えられる[独自研究?]。また、データベース領域では、SQLのように関係式からデータを導き出し情報の位置を抽象化する概念が古くからあり、こちらもプログラミングパラダイムに影響を与えることが 考えられる[独自研究?]

参照

C++における参照は、ポインタと同様「変数がメモリ上に置かれている場所」と解される場合もあるが、それよりもむしろ「その変数を参照する(=変数の値を操作したり出来る)権限」と解されることが多い。参照の概念そのものはメモリの概念と切り離して考えることが可能である(実装上はポインタと同じであることも多い)。

C++における参照の例

int n = 5;
int& n2 = n; // n2をnへの参照で初期化する

この場合変数n2はnを参照している。n2とnはオブジェクトを共有しているのでn2と呼んでもnと呼んでも同じものを表す。すなわち変数nにエイリアス(別名)n2が付いたことになる。

ポインタは未初期化の状態や、何も参照していない状態(ヌルポインタ)が許可されるが、C++における参照は必ず初期化が必要となり、何も参照していない状態は許可されない。ただし、ダングリングポインタと同様、破棄された領域への参照は不正となる。

int& foo() {
    int a = 0;
    return a; // 不正。制御が関数呼び出し元に戻った時点で a は破棄されている。
}

ダングリングポインタ

ダングリングポインタ英語版: dangling pointer)は解放後のメモリ領域を参照するポインタである[13]

メモリ領域は変数のために確保され利用後に解放される。ゆえにその領域が正しい意味を持つのは確保から解放までの間のみであり、解放後にその領域へアクセスすること (Use-After-Free) は未定義の動作を引き起こす。ダングリングポインタは解放後のメモリ領域を参照しているため、このポインタの利用は Use-After-Free であり予測できない結果を生み出してしまう。

防止策が無い場合、ダングリングポインタは容易に生成されうる。例えば、参照を生成し元の変数を free するだけで参照先が解放された状態になってしまう(参照先と参照での生存期間ズレ)。可変長配列のスライス参照ではより複雑で、配列へのpushに伴うメモリ再割り当てで変数の生存期間中なのに最初の領域が解放されるケースがある[14]

このためダングリングポインタはバグの大きな原因であり、それを悪用したハッキングにしばしば利用される。これを事前に防ぐためにモダンなプログラミング言語では言語仕様レベルでのダングリングポインタ防止策が取り入れられている(例: Rust の借用チェッカー)。

スマートポインタ

動的確保したメモリのアドレスをポインタにより管理するとき、注意深くプログラミングしないと解放忘れやダングリングポインタといった問題が発生しやすい。C++ではポインタをクラスでラップし、デストラクタの機構を利用して解放処理を自動化した「スマートポインタ」が利用されることが多い。またオブジェクトの所有権の移動や共有といった、生のポインタでは扱いが難しい概念をスマートポインタの機能として簡潔に実現するライブラリも存在する。

脚注

注釈

出典

  1. ^ a b c ブライアン・カーニハンデニス・リッチー『プログラミング言語C』共立出版ISBN 4-320-02692-6 
  2. ^ 記憶媒体(メモリ)上にある、特定の場所を指し示す識別子のこと。データや命令が保存されているメモリ上の番地表示。
  3. ^ a b c d ロバート・セジウィック(en:Robert Sedgewick (computer scientist))『アルゴリズムC 第1巻 基礎・整列』近代科学社。 ISBN 4-7649-0255-9 
  4. ^ a b c ブライアン・カーニハンロブ・パイク『プログラミング作法』アスキー出版ISBN 4-7561-3649-4 
  5. ^ a b ジョン・ベントリー(en:Jon Bentley (computer scientist))『珠玉のプログラミング』ピアソン・エデュケーションISBN 4-89471-236-9 
  6. ^ 整数(小数点のない数)を格納するためのデータ型。固定小数点を格納するためのデータ型はdecimal型。
  7. ^ "pointer of" ではない。
  8. ^ DCL04-C. ひとつの宣言で2つ以上の変数を宣言しない | JPCERT/CC
  9. ^ NULL - cppreference.com
  10. ^ MEM01-C. free() した直後のポインタには新しい値を代入する
  11. ^ Using and Porting the GNU Compiler Collection (GCC) - C 言語ファミリに対する拡張機能
  12. ^ Using the GNU Compiler Collection (GCC): Warning Options
  13. ^ "解放後のメモリ領域を参照するダングリングポインタ" 池上. (2014). メモリ再利用を禁止するライブラリにより Use-After-Free 脆弱性攻撃を防止する手法の提案. Computer Security Symposium 2014.
  14. ^ "`push` によって `data` の格納先が再割り当てされてしまった ... 単純なスコープ解析では、このバグは防げません。 ... 問題は、その参照を保持している間に、参照先が変わってしまったことです。" rust-lang. 所有権とライフタイム. The Rustonomicon. 2022-12-22閲覧.

関連項目


「ポインタ (プログラミング)」の例文・使い方・用例・文例

Weblio日本語例文用例辞書はプログラムで機械的に例文を生成しているため、不適切な項目が含まれていることもあります。ご了承くださいませ。


英和和英テキスト翻訳>> Weblio翻訳
英語⇒日本語日本語⇒英語
  

辞書ショートカット

すべての辞書の索引

「ポインタ (プログラミング)」の関連用語

ポインタ (プログラミング)のお隣キーワード
検索ランキング

   

英語⇒日本語
日本語⇒英語
   



ポインタ (プログラミング)のページの著作権
Weblio 辞書 情報提供元は 参加元一覧 にて確認できます。

   
ウィキペディアウィキペディア
All text is available under the terms of the GNU Free Documentation License.
この記事は、ウィキペディアのポインタ (プログラミング) (改訂履歴)の記事を複製、再配布したものにあたり、GNU Free Documentation Licenseというライセンスの下で提供されています。 Weblio辞書に掲載されているウィキペディアの記事も、全てGNU Free Documentation Licenseの元に提供されております。
Tanaka Corpusのコンテンツは、特に明示されている場合を除いて、次のライセンスに従います:
 Creative Commons Attribution (CC-BY) 2.0 France.
この対訳データはCreative Commons Attribution 3.0 Unportedでライセンスされています。
浜島書店 Catch a Wave
Copyright © 1995-2025 Hamajima Shoten, Publishers. All rights reserved.
株式会社ベネッセコーポレーション株式会社ベネッセコーポレーション
Copyright © Benesse Holdings, Inc. All rights reserved.
研究社研究社
Copyright (c) 1995-2025 Kenkyusha Co., Ltd. All rights reserved.
日本語WordNet日本語WordNet
日本語ワードネット1.1版 (C) 情報通信研究機構, 2009-2010 License All rights reserved.
WordNet 3.0 Copyright 2006 by Princeton University. All rights reserved. License
日外アソシエーツ株式会社日外アソシエーツ株式会社
Copyright (C) 1994- Nichigai Associates, Inc., All rights reserved.
「斎藤和英大辞典」斎藤秀三郎著、日外アソシエーツ辞書編集部編
EDRDGEDRDG
This page uses the JMdict dictionary files. These files are the property of the Electronic Dictionary Research and Development Group, and are used in conformance with the Group's licence.

©2025 GRAS Group, Inc.RSS