プログラミング言語/C ポインタですー 単純な概念や使い方自体は簡単。 ただ、ポインタに関連するmalloc関枢やnew演算死を使うたびにいちいちfreeだのdeleteだのしなきゃいけない。ポインタとは直接関係あるわけじゃないけど ちょっとでもおかしな部分があればメモリリーク(メモリ使いすぎワロタwww) を引き起こす。マジクソ視ね

ポインタ

ではいよいよポインタの説明です。ポインタとはなんでしょうか?
ズバリ言うと、
 ポインタとは、アドレスを入れるための変数である
そう、int x=10;やchar c='s';と基本的に変わらねーよバーカw ただそんだけ。
intなら整数、charなら文字、と入れるものによって変数の型を使い分けてましたよね?あの延長です。
では復唱しましょう。
 ポインタとは、アドレスを入れるための変数である
 ポインタとは、アドレスを入れるための変数である
 ポインタとは、アドレスを入れるための変数である
はい、覚えましたね。

では具体的にポインタはどうやって使うのでしょうか。
一例として、int型変数のアドレスを入れるためのポインタを作ってみます。
int型変数のアドレスを入れるためのポインタを作るには、

int* ポインタの名前;

とします。例えば、

int* ptr;

のように。

それでは実際のプログラムにしてみましょう。

#include <stdio.h>

main(){
 int a = 5;
 int* ptr; /** int型変数のアドレスを入れるためのポインタを用意 **/
 ptr = &a; /** aのアドレスを入れた **/
}

このように使います。まだ何の役にも立ちませんね。

ここでちょっと注意があります。
世の中の9割以上の本やソースコードには、 int* ptr; ではなくて、

int *ptr;

と書いてあります(スペースの位置が違う)。
これは int* ptr; と同じ意味なので慣れればどっちを書いてもいいのですが、初心者のうちは
 int* ptr; と書くことを強くすすめます
これには三つほど理由があるのですが、現時点で言える理由は、

  1. int *ptr; と書くと、int型の変数 *ptr に見える
  2. int* という型が、int型変数のアドレスを入れるための型だと思うと分かりやすい

ということです。

intと同様に、char型変数のアドレスを入れるためのポインタは char* ptr; と宣言します。
またその他の型用のポインタについても、

型の名前* ポインタの名前;

で宣言できます。

ポインタの使い道

さて、ポインタの概念と作り方はわかりましたが、このままでは使い道がサッパリ分かりません。
そこで使い道を説明していきます。

演算子 * について

まず、変数の頭に & を付けるとその変数のアドレスを表したような感じで、
ポインタの先頭に * を付けると「そのポインタに書いてあるアドレスにある変数の、中身」を表します。
アドレスとは変数の住所のことであるでしたから、アドレスの場所にはなにか変数があるはずです。その中身ですね。
「え? * ってポインタを作るときに使うやつでしょ?」はい、その通りです。これがポインタを学ぼうとして諦める人が多い原因ではないかと思っています。~ねーよww

では早く具体例をみて把握してしまいましょう。

#include <stdio.h>

main(){
 int a = 5;
 int* ptr;
 ptr = &a; /** aのアドレスを、ptrに代入した **/
 printf("%d\n", *ptr); /** 「ptrに書いてあるアドレス」にある変数(つまりa)の中身を表示 **/
}

実行結果

5


ここで int *ptr; と書いてほしくないもうひとつの理由が出てきます。
上のプログラムを int *ptr; と書くと、

#include <stdio.h>

main(){
 int a = 5;
 int *ptr;
 ptr = &a;
 printf("%d\n", *ptr);
}

となります。(ポインタを宣言するための * と、中身を得るための * が同じものに見えて混乱する。)

しかし数学的センスのあるあなたはこう考えます。
「そうか! *ptr が int型の変数だ と思えばいいんだ!」
確かに5行目( int *ptr; )と7行目( printf(...) )を見るとそう見えます。が、そう考えると、

ptr = &a;

が意味不明になります。つい

*ptr = a;

としてしまいそうです。
これがなぜ間違いか考えてみましょう。
5行目(int *ptr;)までの状態では、ptrには「何が入っているか分からない状態」になっています。(変数は宣言しただけでは何が入っているか分からない。)
なので、この状態で *ptr = a; とすると、「ptrに書いてあるアドレス(どっか適当なアドレス)に変数があると思って、そこにaの値5を代入する」という動作を行います。
これはいけませんよね。

本題

さて、だいぶ * の説明が長くなってしまいましたが、いよいよポインタの使い道です。
次のコードがどんな結果を生じるか考えてみてください。

#include <stdio.h>

void inc(int x){
 x = x + 1;
}

main(){
 int a = 5;
 inc(a);
 printf("%d\n", a);
}

さてどうでしょうか?一見6が表示されそうですが、残念ながらそうはなってくれません。
学歴が低い人がinc を呼び出すと、「 x に a の値がコピー」されますが、この「コピー」がくせものです。
例で理解しましょう。
 いま、あなたがmain関数だと思いましょう。あなたは a という名前の紙を持っていて、5と書いてあります。
 あなたにはincという友達がいます。あなたは彼の持っている紙(名前はx)に「 a をコピーして」渡します。
 そして彼が x にかいてある 5 を 6 に書き換えて、その後あなたは a の中身を表示します。 さて、どうなるでしょうか?
そうです。この方法ではaの値変わらないのです。

ここでポインタの登場です。上のプログラムをポインタを使って次のように書き換えました。どうなるでしょうか?

#include <stdio.h>

void inc(int* x){
 *x = *x + 1;
}

main(){
 int a = 5;
 inc(&a);
 printf("%d\n", a);
}

友達のinc君が持っている int* 型の変数x(int型変数のアドレスを入れるためのポインタx)には、aのアドレス(&a)がコピーされて入っています。
この状態でincは *x に、 *x + 1 を代入します。
*x は「xに書いてあるアドレスにある変数の、中身」でした。さらに x には a のアドレスが入っています。つまりこれは

(aの中身) = (aの中身) + 1;

となり、めでたくmainの持つ a が 6 に書き換わります!

このように、ポインタは呼び出された関数から呼出し元の持つ変数の内容を変えたいときなどに利用されます。

まとめ

重要ポイントのまとめです。

 アドレスとは、変数の住所のことである
 ポインタとは、アドレスを入れるための変数である
 *ptr とは、ptrに書いてあるアドレスにある変数の、中身である

分からなくなったときはこれを思い出してください。

最後にちょっと注意

int* ptr; と書け と強くすすめましたが、ちょっとだけ問題があります。
それは次のようにポインタをいくつかまとめて宣言する場合です。

int* a, b, c;

これは

int *a, b, c;

と同じだとみなされて、b と c はただのint型になってしまいます。なのでこういうときは諦めて

int *a, *b, *c;

と書いてください。
(本当は (int*) a, b, c; と書きたいんだけど、こうかくとキャストだと思われてエラーになってしまう。残念。)

ポインタのいろいろ

演算子 -> について

さて、今、ある構造体と、それを指すポインタ(その構造体のアドレスを入れる変数)を用意したとします。

struct PC{
 double CPU_Hz;
 int Monitor_size_tate;
 int Monitor_size_yoko;
 int HDD_GB;
};

main(){
 struct PC myPC = {1.66, 1024, 1240, 500};
 struct PC* PC_ptr = &myPC;
}

このとき、PC_ptr経由でmyPCのメンバにアクセスするにはどうすればいいでしょうか?

printf("%f\n", PC_ptr.CPU_Hz);

こうでしょうか?

ポインタの基礎でやった通り、PC_ptr の中には myPC のアドレスが入っているのであって、構造体の本体は入っていません。ですからこれではダメです。
では構造体の本体はどこにあるかというと、「ポインタに入っているアドレスの指定する先」です。
その中身を取得するには、演算子 * を使うのでした。なので、正しくは以下のようになります。

 printf("%f\n", (*PC_ptr).CPU_Hz);

これで「PC_ptrに入っているアドレスにある構造体(myPC)の、CPU_Hz」が取得できました。

さて、(*PC_ptr).CPU_Hz のカッコがいるのかという問題ですが、残念ながらいります。
なぜなら、 10*1.5 を正しく 10*(1.5) と解釈( (10*1).5ではなく )するようにするために、「* よりも . の方が優先度が高い」ようにC言語がなっているからです。
なので、*PC_ptr.CPU_Hz とすると、*(PC_ptr.CPU_Hz) だと解釈されてしまいます。
これがどういう問題を引きこすかというと、例えば構造体がさらに他の構造体へのポインタをメンバとして持っている場合、

(*(*ptr1).ptr2).member

のようになり、非常に煩雑でわかりにくくなってしまいます。

そこで、救世主 -> の登場です。
この演算子を使うと、先ほどの例が次のようにかけます。

printf("%f\n", PC_ptr->CPU_Hz);

これで「PC_ptrに書いてあるアドレスにある構造体の、CPU_Hz」という意味になります。

この演算子が威力を発揮するのは、さきほどわかりにくくなる例で挙げたように、構造体がさらに別の構造体へのポインタを持っている場合です。次のようにかけます。

ptr1->ptr2->member

カッコがなくなってスッキリしました。ポインタが何個連なってもまったく怖くありません!

(*(*(*(*ptr1).ptr2).ptr3).ptr4).member /** -> を使わないとこうなる **/
ptr1->ptr2->ptr3->ptr4->member         /** 分かりやすい!! **/

構造体とポインタを使ったプログラムでは、積極的に -> を使っていくようにしましょう。

 

ここから下は初心者の入門用ではありません

 

関数ポインタ

関数ポインタ

(注意!)これは初学者向けではありません。最低でも関数とポインタの知識を得てからよむこと。

関数のアドレスを保持するポインタです。 実行する関数を動的に変えられたり、関数の配列を作ったり、関数の引数に関数ポインタ・・・・など、いろいろな事ができるようになります。 つまり、func[0]()が「VIPPER死ね」と表示する、func[1]()が、「チョン市ね」と表示する、fun[2]()がエロ画像を表示するetcみたいなことができる。便利! 普通関数の引数には関数はいれられないけど
func(func2){
func2();
}
みたいなことができる!便利!

まずは関数の宣言から見ていきます。

int MyFunc(float);

これはint型の値を返し、引数にfloat型をとる関数の宣言です。余談ですが、関数の宣言もしくは定義には、仮引数に引数名を与えなくても型だけで十分です。 定義時に引数名を省略しても、関数内部で使えないだけで問題はありません。

関数ポインタは、入る関数の引数と戻り値が分かっている必要があります。というのも、関数ポインタの宣言時にそれらの情報が必要だからです。

宣言の形は、

関数の戻り値 (*関数ポインタ名)(関数のとる引数)

です。

上記のMyFunc関数が入る関数ポインタpFuncを宣言するには、

int (*pFunc)(float);

とします。 これは、

今まで秘密にしてきましたが(?)、関数ポインタは通常のポインタと同じく、単なる変数です。もちろん値を入れられます。 入れる値とは、関数のアドレスです。 以下は関数、関数ポインタ、それへの代入の一通りの例です。

int MyFunc_1(flaot a){ return (int)a + 1; }
int MyFunc_2(flaot a){ return (int)a + 99; }
int (*pFunc)(float);
/* .... */
pFunc=MyFunc_1;
printf("MyFunc_1 %d\n",pFunc(2.0f));
pFunc=MyFunc_2;
printf("MyFunc_2 %d\n",pFunc(1.0f));

実行結果は

MyFunc_1 3
MyFunc_2 100

となります。

関数ポインタですが、変数というからには型があります。型があるからには、もちろんtypedefをする事ができます。typedefとは既存の型に好きな別名を与えるということでしたね。 では、関数ポインタの型とは何なのでしょうか。

下のpFuncの場合、

int (*pFunc)(float);

型は

int (*)(float);

です。どう見てもアナルです。爆発はしません。

typedefの使い方は、

typedef int INTEGER;

これでintの別名"INTEGER"の完成でしたね。

よく見てください。

int INTEGER;

後ろ部分は、『int型の変数の宣言』と形が同じです。 という事は、関数ポインタのtypedefも、 『int (*)(float)型の変数の宣言』に"typedef"をくっつけたものと考えることができますね。 つまり、下です。

typedef int (*pFunc_t)(float);

これで『int (*)(float)の別名pFunc_t』のできあがりです。 次からは、

pFunc_t pFunc;

として関数ポインタを宣言することができます。 すっきりしましたね。

通常のポインタには、多重ポインタがあります。『ポインタのポインタ』とか、『ポインタのポインタのポインタ』といったものです。

int v=0;
int* p = &v; //int型へのポインタ
int** pp = &p; //int*型へのポインタ
int*** ppp = &pp; //int**型へのポインタ
***ppp = 99; //v = 99

関数ポインタも例外でなく、『関数ポインタへのポインタ』などといった形にすることができます。

typedef int (*pfunc_t)(void);
pfunc_t* ppfunc; //int (**ppfunc)(void)と同等
pfunc_t** pppfunc; //int (***pppfunc)(void)と同等
pfunc_t*** ppppfunc; //int (****ppppfunc)(void)と同等
int (*****pppppfunc)(void); //pfunc_t**** pppppfuncと同等

こんなに多重ポインタを作ることはまずありえませんが、『関数ポインタのポインタ』は別のスコープで関数ポインタの内容を動的に変えるのに使えそうです。

関数の引数に関数ポインタのポインタを渡して見る例です。

#include <stdio.h>

int add(int x,int y){ return x+y; } //足し算をする関数
int mlt(int x,int y){ return x*y; } //掛け算をする関数

typedef int (*pfunc_t)(int,int);
void set_1(pfunc_t*f){ *f=add; return; }
void set_2(pfunc_t*f){ *f=mlt; return; }

int main(void){
    pfunc_t pf;
    set_1(&pf);
    printf("add %d\n",pf(99,1));
    set_2(&pf);
    printf("mlt %d\n",pf(33,3));
    return 0;
}

pfunc_t pfの宣言外部で、pfの内容を操作しています。 実行結果は次のようになります。

add 100
mlt 99

C++に対するアプローチ

(注意)ここはCの範囲ではありません、Cではできないことをやってます。
いやむしろなぜCの項に用意したんだよww(関数ポインタの有用性をアピールしたいのでしょうが)

関数オーバーロード時の動作

関数Functionをオーバーロードしています。

#include <stdio.h>

void Function(long int){
    printf_s("long intはintよりも大きいから豊乳じゃと!? \n");
}
void Function(short){
    printf_s("shortはintよりも小さいからは貧乳じゃと!? \n");
}
void Function(void){
    printf_s("ツルペタ始まったな\n");
}

int main(void){
    {
        void (*pfunc)(short);
        pfunc=Function; //コンパイル時に型が自動推論される
        pfunc(short());
    }
    {
        void (*pfunc)(void);
        pfunc=Function; //コンパイル時に型が自動推論される
        pfunc();
    }
   return 0;
}

実行結果は

shortはintよりも小さいからは貧乳じゃと!?
ツルペタ始まったな

となります。

メンバ関数に対する動作

メンバ関数のポインタをとってみましょう。

class CFoo{
public:
    void func_v(){
        printf_s("仮想関数だと!?\n");
    }
    static void func_s(){
        printf_s("ぼくは静的関数ちゃん!\n");
    }
};
/* .... */
void (*pfunc)();
pfunc=CFoo::func_v; //ここでコンパイルエラー

CFoo::func_vはCFooのインスタンスがないと実行できない関数です。 staticがないので当たり前ですね。

では以下はどうでしょうか。 インスタンスを生成してメンバ関数をとってみます。

void (*pfunc)();
CFoo a;
pfunc=&a.func_v; //error C3867 , error C2440

これもエラーです。エラー内容を見てみましょう。

:error C3867:"CFoo::func_v"のポインタを取得したかったら"&CFoo::func_v"ってやってください><

どうやら、ポインタを取得するには"a.func_v"ではなく、"&CFoo::func_v"としないと駄目なようです。
でもこれではせっかくインスタンスを作ったのに意味がありませんね。

:error C2440:"a.func_v"は"void(__thiscall CFoo::*)()"型だから"void (*)()"型には代入できません><

という事は"pfunc"を"void(__thiscall CFoo::*pfunc)()"と宣言し、"&CFoo::func_v"を代入すれば解決でしょうか。
結論から言えば、理論的にはこれで解決です。コンパイルできます。実行もできます。
ただし、重要な問題が発生します。
それは、この場合のpfuncは『関数ポインタ』ではなく『メンバ関数ポインタ』だという事です。なので関数ポインタのような使い方はできません。
メンバポインタについては『関数ポインタのC++に対するアプローチ』という趣旨からほぼ外れるので、ここでは説明を割愛します。各自Google先生に訊いて下さい。分かりやすい説明が出てくると思います。

追記:
gccで同様のコードをコンパイルしたところ、

error: ISO C++ forbids taking the address of a bound member function to form a pointer to member function.
Say ‘&CFoo::func_v’

とのエラーが出ます。(訳:ISO C++では、インスタンスのメンバ関数のアドレスを、メンバ関数へのポインタを作るために取得することは禁じらています。&CFoo::func_vとしてください。)
要は仕様で禁止されている、ということです。
理由は知りません。ごめんネ

第二番目の説明!!!!!うおおおおおおお~~

アドレス

int i = 10;

というコードがあるとします。 これがコンパイルされたとき、EXEのなかではどう表現されているのでしょうか?

変数の値は、実際にはメモリのどこかに保存されています。 そして「どこに保存されているか」というメモリの住所は、番号で表現されます。 上記のコードは、たとえば

45450721番目のメモリに 10 を代入せよ

のようにコンパイルされます。 何番目のメモリを使うかは、コンパイラが勝手に決めてくれます。

C言語を使えば、このようにどの変数にどこのメモリを割り当てるかを 自動的に割り振ってくれるわけですね。

この、"何番目のメモリ" という番号のことを「アドレス」と呼びます。直訳すれば "住所" ですね。

ポインタ

さて、上記のコードにおいて、変数 i が何番目のメモリに格納されているかが知りたいときがあります。 用語を用いて言い換えれば、変数 i のアドレスが知りたいわけですね。

そういう時は &i と書けばおkです。上記の例では &i は 45450721 です。

C言語には、アドレスを格納するための特別な変数型があります。それを「ポインタ」と呼びます。 ポインタの宣言は

int *p;

のように書きます。たとえば

int i = 10;
int *p = &i;

などとします。この p には、たとえば 45450721 などが格納されます。 この例では、p のことを「i へのポインタ」といいます。 「i へのポインタ」とは、"i のアドレスが格納されているポインタ"のことです。

では 45450721番目のメモリに入っている値(すなわち i に格納されている値)が知りたいときはどうするかというと

printf("%d", *p);

と書きます。

*p のことを、「p が指す値」と呼びます。

ポインタの宣言

int i;

という宣言文は「i は int型である」と宣言しています。これと同じで

int *p;

という宣言文は「*p は int型である」、もしくは「pとは、*p が int型になるような型の変数である」と宣言しています。

ちょっと複雑な例を考えましょう。intへのポインタを10個並べた配列 ap が欲しいときはどうすればよいでしょうか?

ap[index] はポインタですね。その指す値(int型)は、 *(ap[index]) で得られます。ですから ap の宣言は

int *(ap[10]);

と書けばよいのです。

次に、intを10個並べた配列へのポインタ pa が欲しいときはどうすればよいでしょうか?

*pa は配列ですから、(*pa)[index] は int 型ですね。ですから

int (*pa)[10];

と宣言すればよいです。

関数の引数について

値渡し

#include <stdio.h>

void func(int a)
{
    a = 100;
}

int main()
{
    int b = 1;
    func(b);
    printf("%d", b);
}

というプログラムを実行すると、"1" と表示されます。決して "100" にはなりません。

具体的に追っていきましょう。&a は 45450721、&b は 6741 だとします。 main関数のなかでは、まず

int b = 1;

で 6741番目のメモリが 1 になります。次に

func(b);

では、45450721番目のメモリに 1 がコピーされて func の先頭にジャンプします。

func のなかで、45450721番目のメモリに 100 が代入されます。そうして

printf("%d", b);

に戻ってきたとき、6741番目のメモリには何が入っているでしょう?

1 ですね。

このように、関数の引数には、呼び出し元の変数の値がコピーされ(「値渡し」)、 関数内部ではそのコピーされたものしかいじれません

関数の引数にポインタを用いると

#include <stdio.h>

void func(int *p)
{
    *p = 100;
}

int main()
{
    int b = 1;
    func(&b);
    printf("%d", b);
}

を最初から追っていきましょう。&p は 45450721、&b は 6741 だとします。 まず

int b = 1;

で 6741番目のメモリが 1 になります。つぎに

func(&b);

で、45450721番目のメモリに 6471 がコピーされて、funcの先頭へジャンプします。

funcのなかで、45450721番目のメモリに格納されている番号(6471番)の メモリに 100 が代入されます。そうして

printf("%d", b);

に戻ってきたとき、6741番目のメモリには 100 が入っています。 ですから、画面には "100" と表示されることになります。

このように、ポインタを関数の引数に用いると、 関数内で呼び出し元の変数をいじることができるようになります。

配列

配列

char ac[100];

と配列を宣言したとします。配列は、メモリ上に連続して並べられます。 &(ac[0]) が 45450721 だとすると

&(ac[0]) = 45450721
&(ac[1]) = 45450722
&(ac[2]) = 45450723
&(ac[3]) = 45450724
.
.
.

とならびます。そうして、ac を単独で用いると、&(ac[0]) に変換されます

printf("%p\n", ac);       // %d が int を表示するように、%p は アドレスを表示する
printf("%p\n", &(ac[0]));

このコードを実行すると、同じ値が表示されます。 このように、配列の識別子は、その配列の先頭のアドレスに変換されます。 変換されるだけで、配列がアドレスそのもの、というわけではありません。 また、先頭のアドレスが格納されているポインタから、 配列へ逆変換することはできません

配列とポインタ

さて、上記の ac を使って

*(ac + 3);

は何を表すでしょう? ac + 3 は 45450724 ですから、 それに * をつけると ac[3] にアクセスできますね。 一般に

ac[index] ⇔ *(ac + index)

の関係が成り立ちます。

ac は &(ac[0]) に変換されるといいましたが、ac[0] は char 型ですから、 &(ac[0]) は char へのポインタです:

char ac[100];
char *pc = ac;

と書くことができます。配列をポインタに代入しているように見えますが、 実際には配列の先頭のアドレスをポインタに代入しているだけです。

このポインタを使って、ac の要素にアクセスすることを考えましょう。 たとえば ac[3] にアクセスしたければ

*(pc + 3)

と書けばよいですね。この節を先頭から読み直すと、これが

pc[3]

と書ける気がしませんか? 書けます。ですが pc[3] は、単なる *(pc + 3) の言い換えに過ぎません

ac[3] は、「配列 ac の 3 番目の要素」を指しますが、 pc[3] は、単に*(pc + 3) を言い換えただけです。

この違いは今は気になりませんが、2次元配列を考えたときに表に浮上します。

何はともあれ、ポインタをつかって配列と同じような書き方ができるのですね。 でもポインタは(ここでは)配列の要素を指し示すものであって、配列そのものとは異なります。次に

char *pc = ac + 5; // &(ac[0]) + 5 のこと

とすれば、pc[3] ⇔ *(pc + 3) ⇔ *(ac + 5 + 3) ⇔ ac[5 + 3] となります。 このように、ポインタを使って、配列の先頭をずらしたように 見せかけることができます。あくまで見せかけるだけです。

配列を関数に渡すには

配列を関数に渡したいとき、仮引数の宣言は 2 種類あります。

void func(char pc[]);
void func(char *pc);

これはどちらも全く同じです。ポインタの宣言になっています。

そしてこの関数を呼び出すとき、配列そのものを渡すことはできません。 代わりに配列の先頭の要素のアドレスを渡します。

char ac[10];
func(ac); // func(&(ac[0])); だとみなされる

それでもこれまで見てきたように、func のなかで このポインタをあたかも配列のように用いることができるので、問題ありません。

オブジェクトのサイズ

話は変わりまして、今度はオブジェクト(≒変数)のサイズの話に移ります。

char, short, int, long

これらは全て整数を表しますが、どうしてこんなに沢山の種類があるのでしょうか?

整数をノートに記録するとき、 大きな数は沢山の桁数が必要です。 "4" なら1桁ですみますが、"123456" なら 6 桁も必要です。

コンピュータでも同じで、大きな数を表すには沢山のメモリが必要です。 使用するメモリ量の順に char ≤ short ≤ int ≤ long となります。 つまり、char より long のほうが、大きな数まで表すことができるのです。

メモリ量の最小単位を「1バイト」といいます。

たとえば、Intel 32bit CPU ならば、int は4バイトです。

int i = 200000000;

と書くと、i の内容は 4 つに分割して格納されます。 たとえば &i が 45450721 なら、45450721番 ~ 4545074番のメモリが使用されます。

そうして、&i は使用されているメモリのうち、先頭の番号を表すのです。

これまで「○○番目のメモリに格納される」と表現してきましたが、 これは正しくは、「○○番から××番までのメモリに格納される」と表現するべきです。 (が、誤解の生じない限り、これからも従来の表現を用います)

ポインタの加減算

上のほうで

char ac[100];

と宣言したら、5 番目の要素のアドレスは ac + 5 で得られる、といいました。 これは char 型が1バイトだからです。ですが、int 型の配列を

int ai[100];

と宣言したとしましょう。そして &(ai[0]) は 45450721 だとしましょう。 すると、(1 つの要素の大きさが 4 バイトなので) 5 番目の要素のアドレスは 45450721 + (5 * 4) になります。

では 5 番目の要素のアドレスは ai + (5 * 4) と書かないといけないのでしょうか?

実はこれは間違いです。単に

ai + 5

と書くだけでよいのです。x4 の部分は、コンパイラが勝手に行ってくれます。

同様に

int ai[100];
int* pi = ai;
++pi;

というコードを実行すると、&(ai[0]) が 45450721 ならば、pi は 45450725 に なります。++pi; は単なるインクリメントではなく、 ポインタが指す型のサイズ分だけ加算されるのです。

逆に言えば、ポインタを使うときは、コンパイラがポインタが指す型のサイズを 知っている必要があります。

配列再び

今度は2次元配列について考えます。

2次元配列

char aac[3][2];

と2次元配列を宣言したとします。するとこれは 「要素が2つの配列を、3つ並べたもの」になります。

aac[0][0], aac[0][1], aac[1][0], aac[1][1], aac[2][0], aac[2][1]

の順に、全ての要素が連続して並べられます。

そして &aac[0][0] が 45450721 だとすると、aac[x][y] のアドレスは

45450721 + (x * 2) + y

で計算できます。

aac[x] と一つ目の添え字だけを指定すると、{ aac[x][0], aac[x][1] } とならんでいる部分を char 型の配列だとみなしたものが得られます。

すなわち aac[x] は &(aac[x][0]) に変換されます。

char* pc1 = aac[1];

などと書けるわけです。pc1 の値は、実際には

45450721 + (1 * 2)

と計算されます。

そうして pc1[x] ⇔ aac[1][x] になります。

では aac を単独で用いたときは、何に変換されるのでしょうか?

aac[x] が char 型の配列なのですから、aac は「char型の配列」の配列です。 ですから、aac は、「char 型の配列」へのポインタに変換されるべきです。

配列へのポインタは上のほうでやりましたね。

char (*pac)[2] = aac;

と変換できるわけです。型はややこしいですが、値は 45450721 です。 この場合、++pac; などと書けば、要素 2 つの配列の長さ分だけ加算されて 45450723 になります。すると pac[x][y] は aac[x + 1][y] と同じものを指すようになります。

気づいた方がおられるでしょうか?

char aac[3][2];
char (*pac)[2] = aac;
char *pc0 = aac[0];

printf("%p\n%p\n", pac, pc0);

を実行すると、pac も pc0 も同じ値であることがわかります。しかしこの2つは、 値は同じでも型が違うのです。

pac[x] は char 型の配列になりますが、pc0[y] は char 型の変数になるのです。

関数に渡すには

関数の引数に int aai[3][2] を渡したいとしましょう。仮引数の宣言はどうすれば よいでしょうか?

配列を渡すには、その先頭の要素へのアドレスを受け渡しするようにすればよいのでしたね。

今回の場合、aai は「配列の」配列ですから、受け取る側は「配列の」ポインタで なければいけません。すなわち

void func(int (*pai)[2]);

と宣言します。もっと簡単に

void func(int pai[][2]);

と書いても同じです。この "2" を省略することはできません。なぜなら、

++pai;

というコードがあったとき、「要素 2 つの int 型配列」分の 大きさだけ加算する必要があります。同様に

pai[2]; // *(pai + 2) のこと

というコードがあったとき、「pai の値よりも『要素 2 つの int 型配列』分の大きさ x2 だけ 進んだところ」から始まる配列を取得しないといけません。

このように要素 2 つ、というのは重要な情報なのです。

似非2次元配列

お気づきの方はいるでしょうか? main 関数の宣言は

int main(int argc, char *(argv[])); // これは char *argv[] と同じ
int main(int argc, char (*argv)[]);
int main(int argc, char **argv);
int main(int argc, char argv[][]);

などと書きます(全部同じです。char **argv; が宣言されたことになります)。 最後の宣言方法を見るとわかりますが、argv[][ここがない] ですね。

普段 argv[1][0] などと2次元配列と同じように用いていますが、 これは本来の2次元配列ならありえない話です。

なぜなら、argv[1] というのが、argv[0] から「要素いくつ分の char 型配列」分の大きさだけ 離れたところにあるか分からないのですから。

実は argv に渡されるのは、2次元配列ではなく、「ポインタがならんだ配列」なのです。

これの宣言は次のようにします。

char ac0[3];
char ac1[6];
char ac2[2];
char *(apc[3]) = { ac0, ac1, ac2 }; // { &(ac0[0]), &(ac1[0]) &(ac2[0]) }; と同じ

apc[x] は、配列先頭の要素へのポインタですから

*(apc[x] + y)

もしくは

apc[x][y]

と書けば、x 番目の配列の、第 y 要素が得られることになります。

この場合、コンパイラは要素にアクセスするために、アドレスを計算する必要がありません。 そのアドレスは最初から配列内に格納されているのですから。

そして、「ポインタがならんだ配列」を関数に渡したいときは、その配列の先頭要素(=ポインタ)の アドレスを渡すのでしたね。ですから関数の仮引数は「ポインタへのポインタ」として 宣言しないといけません。それが

void func(char **ppc);
void func(char *(ppc[]));

などとなるのです。そのほかの書き方でもかまいません。

ちなみに、今回の変数を使って

printf("%p\n%p\n", apc, apc[0]);

を実行してみてください。前回と違い、今度は違うアドレスが表示されるはずです。

関数ポインタ

関数のアドレス

プログラムが実行されているとき、変数の値はメモリのどこかに格納され、 そのメモリの番号をアドレスというのでした。 今回は、そのプログラムのコード自体に焦点を当てます。

実はプログラムというのは、メモリにロードされて実行されます。 つまり、プログラム自体もメモリのどこかに格納されているのです。

プログラム中で関数を使うなら、その関数自体がメモリのどこかに格納されています。

そこで、関数が格納されているメモリ領域の、先頭の番号を「関数のアドレス」と 呼ぶことにしましょう。

関数の呼び出され方

void func(void)
{
    printf("hello, world!\n");
}

int main()
{
    func();
}

というプログラムがあって、func のアドレス(func の先頭のアドレス) が 45450721 だとしましょう。すると main 関数内の

func();

という行は「45450721番目のメモリに格納されているコードへジャンプしろ」と翻訳 されます。

ですから、アドレスさえわかっていれば、その関数を呼び出すことが可能なのです。

関数ポインタ

そこで、関数のアドレスを格納するポインタ pfn があるとしましょう。 このポインタを使って、関数を呼び出すには

int result = (*pfn)(x, y);

などと書けばよいのです。ただし

int result = pfn(x, y);

と書いても全く同じに解釈されます。*をつけてもつけなくてもよいのですね。 この関数ポインタ独特の仕様は、関数ポインタをわかりにくくする一端を担っている 気がします。

さて、このように使う pfn を宣言するにはどうすればよいのでしょうか?

今までと同じで「(*pfn)(int型, int型) (の返り値)は int だ」と宣言すればよいのです:

int (*pfn)(int, int);

先ほど述べたように pfn(int, int); と呼び出してもよいのですが

× int pfn(int, int);

では、pfn という関数のプロトタイプ宣言になってしまうので、関数ポインタを宣言するときは 必ず前者の方法を取ります。

関数ポインタに関数のアドレスを代入するには

int (*pfn)(int, int) = func; // func は int func(int x, int y){ ... } となっている必要がある

と書けばよいです。簡単ですね。

関数ポインタの使い道

int Kakeru(int x, int y)
{
    return x * y;
}

int Tasu(int x, int y)
{
    return x + y;
}

int Enzan(x, y, int (*pfn)(int, int))
{
    return pfn(x, y);
}

int main()
{
    printf("%d, %d\n",
        Enzan(3, 4, Kakeru),
        Enzan(3, 4, Tasu)
    );
}

のように使えます。Enzan という関数ひとつで、掛け算と足し算のオプションが 選べるようになるわけです。

C++にステップアップしたときに、virtual 関数というものに出会うことでしょう。 それは関数ポインタを用いて実装されているのです。

練習問題

つぎの文章に答えろ この程度のクイズに答えられないならこのページをもう一度全部読み直せ

第一問 int* a;と宣言したとき、aの型はint型である
○ or ×

第二問
int x=10;
int* p;
p=&x;
p[0]=20;

pは配列ではないので、これはエラーが出てしまう
○ or ×

第三問 int x=20; 変数xはスタック領域におかえるかヒープ領域におかれるか

第四問 ポインタと配列は同じものなので char* c="VIPPER"; char c[]="VIPPER"; は同じ意味である。○ or ×

第五問 int** p; p=(int)x; このようなことはやってはいけない。○ or ×

第六問 int* p; int a[10][10][10]; p=(int*)a; p[500]=345;

こうするとa[?][?][?]が500に変わる。どこか。


トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2023-02-23 (木) 23:33:35