ホームに戻る
ポインタの話
ポインタの挙動を整理するために書いてみました。
純粋にCの話なのでスマートポインタなどの話はありません。
#include <stdio.h>
#include <stdlib.h>
/*
以下の話は bcc32 を基準に書かれています。
*/
/*
00、変数とポインタの話
扱うものは「値」もしくは「アドレス」のどちらかである。
「値」は数値そのものという認識で良い。
「アドレス」とは「値」のあるメモリ上の位置のこと。
ひとつの「値」に対して必ずひとつの「アドレス」がある。
普通は「アドレス」を直接数値で見ることは無いが
printf で %p の書式を使うと見ることができる。
変数 a は、
int a;
と定義する。
変数 a の値は、
a
と書く。
変数 a のアドレスは
&a
と書く。
ポインタ p は、
int *p;
で定義する。
ポインタ p の指す値は、
*p
と書く。
ポインタ数 p の指すアドレスは
p
と書く。
値に値、アドレスにアドレスは代入できる。
しかし、値にアドレス、アドレスに値は代入できない。
キャストをすることにより可能ではあるが、
特別な理由がなければ入れ替えて代入することは無い。
*/
// 変数とポインタについて
void f_00(){
int a = 3;
int b = 5;
int *p;
p = &a; // a のアドレスをポインタ p に入れる
b = *p; // p の指すアドレスの値を b に入れる よって b = 3
// a = p; // これは不可
// *p = &a; // これも不可
printf("%d\n", a); // 3 が表示される。
printf("%d\n", b); // 3 が表示される。
printf("%d\n", *p); // 3 が表示される。
printf("%p\n", p); // p が指すアドレスが表示される。
p = (int *)123; // 可能ではあるが、使う理由があること!
}
/*
01、値渡しとポインタの話
「値渡し」とは値をコピーして渡すことである。
例えば、
void f(int n){
n = 3;
}
int main(){
int a = 1;
f(a);
printf("%d\n", a);
return 0;
}
このとき表示されるのは 1 である。
これは引数に a を渡しているが関数内では
a の値が n にコピーされて関数内では n の値として使用される。
よって n の値を書き換えても a の値に影響しない。
このようにコピーされる場合を「値渡し」という。
ポインタを使うと n で a を書き換えることができる。
void f(int *n){
*n = 3;
}
int main(){
int a = 1;
f(&a);
printf("%d\n", a);
return 0;
}
このとき表示されるのは 3 である。
これは a のアドレスを「値渡し」することになるので、
n には a のアドレスが入る。
よって n の指す値 *n に 3 を入れると、
結果として a の値も 3 になったことになる。
配列は全体を「値渡し」することはできない。
もし「値渡し」のようなことをしたい場合には、
ポインタを渡して自力でコピーするしかない。
もし全体を「値渡し」したい場合には
一般的では無いかもしれないが構造体を使うことで可能になる。
typedef struct{
int d[2];
}st;
void f(st s){
s.d[1] = 7;
printf("0:%d\n", s.d[0]);
}
int main(){
st s;
s.d[0] = 3;
s.d[1] = 5;
f(s);
printf("1:%d\n", s.d[1]);
return 0;
}
このとき 0:3 と 1:5 が表示される。
ただし、構造体にも注意点がある。
次のような構造体の場合はポインタのアドレスの「値渡し」になり。
ポインタの指す値を「値渡し」するわけではないので注意する。
typedef struct{
int *p;
}st;
*/
void f_01_1(int fa, int *fb){
fa = 4;
*fb = 6;
}
// 値渡しと参照渡しについて
void f_01(){
int a = 3;
int b = 5;
f_01_1(a, &b);
printf("%d\n", a); // 3 が表示される。
printf("%d\n", b); // 6 が表示される。
}
/*
02、構造体のポインタの話
構造体の要素には . でアクセスする。
構造体のポインタは要素に -> でアクセスする。
特に、
typedef struct{
int a;
int *p;
}st;
st s;
st *ps;
のとき、
*ps->p は *(ps->p) のことなので注意する。
*/
typedef struct{
int a;
int *p;
}st;
// 構造体のポインタについて
void f_02(){
int n = 5;
st s;
st *ps;
s.a = 3;
s.p = &n;
ps = &s;
printf("%d\n", ps->a); // 3 が表示される。
printf("%d\n", *ps->p); // 5 が表示される。
printf("%d\n", *((*ps).p)); // 5 が表示される。
}
/*
03、ポインタとサイズの話
変数の場合はサイズとは変数そのものが入るサイズをあらわす。
よって、変数型によってサイズが異なる。
(注:変数型のサイズは処理系により異なるので注意。)
ポインタのサイズは処理系により1つに定まる。
例えば sizeof(char *) も sizeof(int *) も同一である。
bcc32 の場合、32ビットであるのでサイズは 4 である。
これは 4 バイトまでのアドレスを記録できることをあらわす。
では (char *) の 「char」 を指定する意味はないのであろうか?
これは例えばポインタを加算して進める場合などに役にたつ。
例えば、char が1バイトのとき、
char *p;
p = p + 1;
と書いたときアドレスは 1 加算される。
ただし、int が4バイトのとき、
int *p;
p = p + 1;
と書いたときアドレスは 4 加算される。
*/
// ポインタのサイズ
void f_03(){
char c;
int i;
char *pc;
int *pi;
printf("%d\n", sizeof(c)); // 1 が表示される
printf("%d\n", sizeof(i)); // 4 が表示される
printf("%d\n", sizeof(pc)); // 4 が表示される
printf("%d\n", sizeof(pi)); // 4 が表示される
int a[3] = {1, 2, 3};
int *p;
p = &a[1];
printf("%d\n", *(p - 1)); // 1 が表示される
printf("%d\n", *(p + 0)); // 2 が表示される
printf("%d\n", *(p + 1)); // 3 が表示される
}
/*
04、ポインタと文字列、配列の話
char *ps = "abc";
と書くとき。
"abc" はメモリ上に 'a'、'b'、'c'、'\0' を連続で作成し、
先頭のアドレスを ps に渡すということをやっています。
'\0' はヌル文字といい文字終端のラベルとして使う。
注意:
bcc32 では "abc" は書き換え可能であるが、
gcc や vc++ では "abc" は書き換えることができない。
char *ps = "abc";
と書くと ps[0] 、ps[1] と書いたときに
その位置の値にアクセスすることができる。
アドレスではなく値にアクセスするところに注意する。
ここで、
char *ps = "abcde";
と
char s[] = "abcde";
は大きく挙動が異なるので注意する。
char s[] は新しい領域が作成され abc が入る。
このときの s はポインタではなく配列の名前でしかない。
sizeof(ps) は 4 でありアドレスのサイズである。
sizeof(s) は 6 であり文字列の長さ+ヌル文字のサイズになる。
ただし、 s も s + 1 などとするとポインタとして扱われるので注意。
sizeof(s + 1) は 4 でありアドレスのサイズになる。
char s[] の場合は値の書き換えが可能である。
char *ps の場合も char s[] の場合も
ps[0] と s[0] と書けて見た目が同じになるので注意する。
*/
// ポインタと文字列、配列について
void f_04(){
char *ps = "abcde";
char s[] = "fghij";
printf("%s\n", ps); // abcde が表示される。
printf("%s\n", s); // fghij が表示される。
printf("%c\n", ps[1]); // b が表示される。
printf("%c\n", s[2]); // h が表示される。
printf("%d\n", sizeof(ps)); // 4 が表示される。
printf("%d\n", sizeof(s)); // 6 が表示される。
printf("%d\n", sizeof(s + 0)); // 4 が表示される。
// これも通る
printf("%c\n", 2[s]); // h が表示される。
}
/*
05、malloc と free の話
malloc は次のように書く
int *p = (int *)malloc(3 * sizeof(int));
メモリが確保できなかったときに p は NULL になる。
よって malloc をした場合には
p が NULL かどうかを調べる必要がある。
free は malloc で確保したポインタのみに行う。
malloc で確保した領域の先頭ポインタは加算はできない。
これは加算したポインタでは正常に free できないため。
加算したい場合はコピーを作って加算する。
また、2重で free をすることもしてはならない。
よって、2重 free してしまうことを予防
free をしたら NULL を入れておく方法はよく使われる。
*/
// malloc と free について
void f_05(){
static int *p = NULL;
if(p != NULL){
free(p);
p = NULL;
}
p = (int *)malloc(3 * sizeof(int));
if(p == NULL){
return;
}
// p++; は禁止
p[0] = 1;
p[1] = 2;
p[2] = 3;
// p[3] = 4; は領域が確保されていないので禁止
printf("%d\n", p[0]); // 1 が表示される。
printf("%d\n", p[1]); // 2 が表示される。
printf("%d\n", p[2]); // 3 が表示される。
free(p);
p = NULL;
}
/*
06、ポインタのポインタの話
ポインタのポインタの使い道とは?
1、可変サイズのポインタの配列を作りたいとき
2、ポインタの配列を指し示したいとき
などに有効ではないかと考えます。
可変サイズのポインタの配列は例えば数字 n を与えて、
int **p = (int **)malloc(n * sizeof(int *));
のようにして作ることができる。
これは配列では
int *p[n];
のようにできないので必要である。
また文字列の配列を用意したときに先頭を指すときに使える。
例えば、
char *s[3] ={"abc", "def", "ghi"};
としたときに、
char **pp = s;
とすることができる。
ただし、
char *s[3] ={"abc", "def", "ghi"};
char **pp = s;
としたとき
**pp、*pp、pp、pp[0]、*pp[0]、(*pp)[0]、pp[0][0]
というようにいろいろな書き方ができる。
それぞれ何を表すかは下のサンプルで十分検証していただきたい。
特に *pp[0] は *(pp[0]) を意味するので、
(*pp)[0] との挙動が違うことに注意したい。
*/
// ポインタのポインタについて
void f_06(){
char *s[3] ={"abc", "def", "ghi"};
char **pp = s;
printf("%c\n", **pp); // a が表示される。
printf("%c\n", **(pp + 1)); // d が表示される。
printf("%s\n", *pp); // abc が表示される。
printf("%c\n", *(*pp + 1)); // b が表示される。
printf("%p\n", pp); // s の先頭アドレスが表示される。
printf("%s\n", pp[2]); // ghi が表示される。
printf("%c\n", *pp[1]); // d が表示される。
printf("%c\n", (*pp)[1]); // b が表示される。
printf("%c\n", pp[2][2]); // i が表示される。
}
/*
07、多次元配列を関数に渡す
多次元配列は配列なので全体を「値渡し」はできない。
では int d[2][3][4] のポインタを渡して、
関数内でも同じように d[2][3][4] として使うにはどうしたら良いか?
このとき、渡す先の関数を
void f(int d[][3][4]){}
もしくは、
void f(int (*d)[3][4]){}
と書くことで実現することができる。
もちろん
void f(int d[2][3][4]){}
と書いても良いし、こちらのほうがわかりやすい。
*/
// 多次元配列を関数に渡す
void f_07_1(int d[][3][4]){
printf("%d\n", d[1][2][3]); // 7 が表示される。
}
void f_07_2(int d[][4]){
printf("%d\n", d[2][3]); // 7 が表示される。
}
void f_07_3(int d[]){
printf("%d\n", d[3]); // 7 が表示される。
}
void f_07(){
int d[2][3][4];
d[1][2][3] = 7;
f_07_1(d);
f_07_2(&d[1][0]);
f_07_3(&d[1][2][0]);
}
/*
08、ポインタのポインタのポインタ
ポインタのポインタのポインタを使うことはほとんど無い。
ただ、ポインタのポインタのアドレスを関数で渡したい、
ということはあるかもしれない。
また、3次元配列の管理をすることもできる。
ただし要素数の管理が非常に難しくなる。
非常に記述が紛らわしくなるため、
構造体などを上手に使って記述したほうが、
わかりやすいコードになると思われる。
*/
// ポインタのポインタのポインタについて
static int count = 0;
void f_08_1(char ***p, char *s){
char **p2 = (char **)malloc((count + 1) * sizeof(char *));
if(p2 == NULL){
return;
}
for(int i = 0; i < count; i++){
p2[i] = (*p)[i];
}
p2[count] = s;
if(*p != NULL){
free(*p);
*p = NULL;
}
*p = p2;
count += 1;
}
void f_08_2(){
char ***ppp;
char **pps[2];
char *ps1[3] = {"abc", "def", "ghi"};
char *ps2[4] = {"jkl", "mno", "pqr", "stu"};
pps[0] = ps1;
pps[1] = ps2;
ppp = pps;
printf("%c\n", ppp[1][2][1]); // q が表示される。
}
void f_08(){
char **p = NULL;
char *ps[3] = {"abc", "def", "ghi"};
f_08_1(&p, ps[0]);
f_08_1(&p, ps[2]);
printf("%s\n", p[0]); // abc が表示される。
printf("%s\n", p[1]); // ghi が表示される。
f_08_2();
}
/*
09、関数のポインタの話
関数のポインタは関数の開始アドレスを持つ。
関数のポインタはときに非常に有効に使用できる。
例えば、条件によって f1() と f2() の関数を使い分けたいとき、
条件の設定が最初の1回のみで後に変更が無いのであれば、
関数のポインタ
void (*f)();
を作っておけば。
最初に条件を検証しておき、
f = f1;
もしくは
f = f2;
をしておくことで以降 f() でいずれかの関数を呼べる。
これは毎回条件比較を書くことが無いので有効といえる。
もちろん関数のポインタは引数や戻り値の型を統一しなければならない。
*/
// 関数のポインタ
void f_09_1(){
printf("f_09_1\n");
}
void f_09_2(){
printf("f_09_2\n");
}
void f_09(){
void (*f[2])();
f[0] = f_09_1;
f[1] = f_09_2;
f[0](); // f_09_1() が呼ばれる。
f[1](); // f_09_2() が呼ばれる。
}
int main(){
printf("f_00:\n");
f_00();
printf("f_01:\n");
f_01();
printf("f_02:\n");
f_02();
printf("f_03:\n");
f_03();
printf("f_04:\n");
f_04();
printf("f_05:\n");
f_05();
printf("f_06:\n");
f_06();
printf("f_07:\n");
f_07();
printf("f_08:\n");
f_08();
printf("f_09:\n");
f_09();
return 0;
}