ポインタとは?ポインタの使い方は?


ポインタはメモリのアドレスを指すもの

C言語で書いたプログラムをコンパイルして実行ファイルを作るわけだが、どのように変換されるのか見てみよう。以下の内容でhello.c というファイルを作る。

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("hello world\n");
    return 0;
}

これをコンパイルする。

gcc -g -O0 -o hello hello.c

-g (デバッグオプション)、 -O0 (最適化なし)を追加している。実行プログラムができる。

ポインタを理解するために、どのようなマシン語ができているのか見てみたいと思う。objdump –all-headers -s –disassemble –source helloを実行すればよいだろう。

主要な部分を抜き出すと次のようになっている。

_main:
; {
100000f50:	55 	pushq	%rbp
100000f51:	48 89 e5 	movq	%rsp, %rbp
100000f54:	48 83 ec 20 	subq	$32, %rsp
100000f58:	c7 45 fc 00 00 00 00 	movl	$0, -4(%rbp)
100000f5f:	89 7d f8 	movl	%edi, -8(%rbp)
100000f62:	48 89 75 f0 	movq	%rsi, -16(%rbp)
; printf("hello world\n");
100000f66:	48 8d 3d 35 00 00 00 	leaq	53(%rip), %rdi
100000f6d:	b0 00 	movb	$0, %al
100000f6f:	e8 0e 00 00 00 	callq	14 <hello.c+0x100000f82>
100000f74:	31 c9 	xorl	%ecx, %ecx
; return 0;
100000f76:	89 45 ec 	movl	%eax, -20(%rbp)
100000f79:	89 c8 	movl	%ecx, %eax
100000f7b:	48 83 c4 20 	addq	$32, %rsp
100000f7f:	5d 	popq	%rbp
100000f80:	c3 	retq
:
Contents of section __cstring:
 100000fa2 68656c6c 6f20776f 726c640a 00        hello world..

100000f50 などといったものがメモリのアドレス(番地)で、1つのアドレスには1バイトが入る。アドレスにプログラムやデータが書き込まれている。深く理解したければ、100000f50 番地から何をしているか追ってみてもよいだろう。

本当はインストラクションポインタ(プログラムカウンタ)、ヒープやスタックといったものも説明したいところだが、別に説明しようと思う。

ポインタとはアドレスを指すものだ。

ポインタの使い方

ポインタは次のようなケースで使うことだろう。

文字列処理

上で見た例のように、hello world という文字列を表すために、文字列の先頭アドレスとそこから始まり、¥0(00) で終わる領域をC言語では文字列として扱う。

FILE ポインタ

C言語の引数やリターン値は、整数またはポインタになっているであろう。マシン語と対応しているからだろう。fopen / fwrite / fread / fgetc / feof / fclose などを見てほしい。

ファイル入出力の機能を提供するために必要なデータ構造をfopen で FILE 構造体として確保する。しかし、この構造体を表すために、構造体の先頭アドレスを返す。この先頭アドレスを変数に保持することになると思うが、この変数をポインタと呼ぶ。

読み書きの際は、FILE 構造体のポインタを関数(fwrite / fread / fgetc など)に渡すことになる。

scanf の引数

int a, b, c, d;
scanf("%d %d %d %d", &a, &b, &c, &d);

のように使うことだろう。C言語はサブルーチンには値渡し(call by value)しかできない。渡した値をサブルーチンで変更して戻ってきても渡した変数は変わらない。それに値は一つしか返せない。

しかし、上の例のように、変数に値をセットしたいとか、複数返したいといったことがあるだろう。

そういった場合は、変数のアドレス(=ポインタ)を渡してサブルーチン側でそのアドレス(=ポインタ)に値をセットする、といったようにする。

変数を a とするとポインタは &a、ポインタを p とするとポインタが指す値は *p となる。

リストやツリーの表現

例えば、2, 3, 5, 7, 11, 13, .. といったようなデータの並びや、単語の並びといった一連のデータの並びを表す構造である。要素の数はいくつあるかわからないので配列では表現できない。好きな場所のデータを書き換えたり、データを追加、挿入、削除したりすることが考えられる。wikipediaの図がわかりやすいだろう。リストでは隣の要素のアドレス(=ポインタ)を要素内に保持する。

ツリーは二分木などの木構造。これもwikipediaの図がわかりやすい。二分木では左右の要素のアドレス(=ポインタ)を要素内に保持する。

ちなみに、リストを表現するために、queue.hマクロを使いこなせるようになりたい。(man queue を参照。SLIST / STAILQ / LIST / TAILQ などがある。)

それぞれの要素は、malloc で実行時に確保することになるだろう。

おわりに

ポインタがわかりにくければ、

printf(“%p”, ポインタ変数);
printf(“%p”, &変数);

などでアドレスを表示してみると理解が深まることと思う。