値を変数に保存するとき、その値はメモリ上のどこかに保存される。C/C++ 言語では、値が保存されているメモリのアドレスを取得することができる。メモリのアドレスを取り扱うとき、ポインタ型の変数を利用する。例えば、int x = 5;
を実行したとき、5
という値は、メモリの 0x104F86CCFFAF00A2
に保存され、その値が x
として命名される。そのため、変数 x
には、実体(5
)と住所(0x104F86CCFFAF00A2)という 2 種類の情報を取り出すことができる。ある変数のメモリ上のアドレスを別の変数に保存したいときは、ポインタ型の変数を使用する。例えば、変数 x
のアドレスを変数 y
に保存するには、int* y
のように宣言を行い、変数 x
のアドレスを &x
のように取り出して、y
に代入する。
#include<stdio.h>
int main(){
int x = 5;
int* y = &x;
return 0;
}
int 型の変数およびポインタ型の変数を宣言するときに確保されるメモリの大きさは、システムの環境およびコンパイラによって異なる。上図の例では int 型は 4 バイトであり、ポインタ型は 8 バイトである。なお、ポインタ型はアドレスを保存しているため、整数へのポインタや文字へのポインタなどすべて 8 バイトとなる。
ポインタの宣言と初期化
変数とポインタ
変数には、実体と住所という 2 種類の情報を取り出すことができる。住所を取り出すときはアドレス演算子 &
を利用する。
#include<stdio.h>
int main(){
int a = 1;
printf("%d\n", a); // 1
printf("%p\n", &a); // 0x7ffeecde48d8
return 0;
}
ポインタ型変数
メモリアドレスを変数として保存したい場合は、ポインタ型の変数を利用する。ポインタ型の変数を利用することで、メモリ上の値を直接に編集することができ、様々な操作を柔軟に行えるようになる。ポインタ型の変数を宣言するとき、そのアドレスに保存されている型に応じて、int*
や char*
などのように宣言する。ポインタ型の変数にはメモリアドレスが保存される。そのアドレスに保存されている実体(値)を取り出すには間接演算子 *
を使用する。
#include<stdio.h>
int main(){
int a = 1;
int* p = &a;
printf("%p\n", p); // 0x7ffeef4e38d8
printf("%d\n", *p); // 1
char b = 'A';
char* q = &b;
printf("%p\n", q); // 0x7ffeef4e38cf
printf("%c\n", *q); // A
return 0;
}
ポインタと配列
C 言語では、配列の名前が配列の先頭のアドレスを表している。そのため、配列の名前とポインタ変数を同様に扱うことができる。
#include<stdio.h>
int main(){
int arr[5] = {11, 22, 33, 44, 55};
int* p = arr;
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
printf("%d %d %p\t", arr[i], *(arr + i), &arr[i]);
printf("%d %d %p\n", p[i], *(p + i), p + i);
}
return 0;
}
// 11 11 0x7ffeebea08c0 11 11 0x7ffeebea08c0
// 22 22 0x7ffeebea08c4 22 22 0x7ffeebea08c4
// 33 33 0x7ffeebea08c8 33 33 0x7ffeebea08c8
// 44 44 0x7ffeebea08cc 44 44 0x7ffeebea08cc
// 55 55 0x7ffeebea08d0 55 55 0x7ffeebea08d0
ポインタと文字列
C 言語では、文字列を文字の配列として扱うため、上述の配列と同様に、文字列とポインタ変数をほぼ同様に扱うことができる。
#include<stdio.h>
int main(){
char arr[6] = "hello";
char* p = arr;
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
printf("%c %c %p\t", arr[i], *(arr + i), &arr[i]);
printf("%c %c %p\n", p[i], *(p + i), p + i);
}
return 0;
}
// h h 0x7ffee54ac8d6 h h 0x7ffee54ac8d6
// e e 0x7ffee54ac8d7 e e 0x7ffee54ac8d7
// l l 0x7ffee54ac8d8 l l 0x7ffee54ac8d8
// l l 0x7ffee54ac8d9 l l 0x7ffee54ac8d9
// o o 0x7ffee54ac8da o o 0x7ffee54ac8da
// 0x7ffee54ac8db 0x7ffee54ac8db
また、次のように文字の配列を作らずに、直接ポインタに代入することもできる。
#include<stdio.h>
#include
int main(){
char* p = "hello";
for (int i = 0; i < 6; i++) {
printf("%c %c %p\n", p[i], *(p + i), p + i);
}
return 0;
}
// h h 0x10c827f9a
// e e 0x10c827f9b
// l l 0x10c827f9c
// l l 0x10c827f9d
// o o 0x10c827f9e
// 0x10c827f9f
ポインタと関数
関数の引数として、値を渡す方法とポインタを渡す方法などがある。値渡しの場合は、変数の値のコピーを関数に渡すので、関数内でその変数の値を変更してても、関数を呼び出した側の値は変更されることはない。一方で、ポインタ渡しの場合は、変数のアドレスそのものを関数に渡すので、関数内でそのアドレスに対して直接操作できるようになり、そこで変数の値を変更してしまうと、その関数を抜けた後も、値は変更されたままにある。
#include <stdio.h>
void f(int x) {
x += 1;
}
void g(int* x) {
*x += 1;
}
int main(void) {
int x = 10;
printf("%d\n", x); // 10
f(x);
printf("%d\n", x); // 10
g(&x);
printf("%d\n", x); // 11
return 0;
}
関数の引数として、値を渡す方法とポインタを渡す方法のほかに、C++ で追加された機能として参照渡しがある。機能的に、ポインタ渡しとほぼ同じである。
#include <iostream>
void f(int x) {
x += 1;
}
void h(int& x) {
x += 1;
}
int main(void) {
int x = 10;
std::cout << x << std::endl; // 10
f(x);
std::cout << x << std::endl; // 10
h(x);
std::cout << x << std::endl; // 11
return 0;
}