【C言語】ポインタに入門! やっと意味わかったかも!

かねてより何度も挑戦しては理解できなくて挫折していたポインタ。それがついに!理解できた(ような気がする)ので、メモとして残す。

別の変数に値をコピーする(ポインタじゃないパターン)

まず、普通に値をコピーしてみる。ある変数から、別の変数に値をコピーするには、次のようにする。この手順では、コピー元とコピー先は別の実体となる。

#include <stdio.h>
int main(){
    int a;
    int b;

    //  a のコピー b の練習。
    a = 1;
    b = a;
    printf("a: %d; b: %d;\n",a,b);
    b = 5;
    printf("a: %d; b: %d;\n",a,b);

    return 1;
}

これを hellopointer.c というファイル名で保存し、コンパイルして実行してみる。

ちなみに、コンパイルするのは下記のコマンド。(ただし、gccコマンドがインストールされている必要がある)

# gcc -o hellopointer hellopointer.c

こうすると、 hellopointer という実行ファイルができているはずなので、それを実行。

# ./hellopointer

すると下記のように出力されるはずだ。

a: 1; b: 1;
a: 1; b: 5;

int型の変数 ab を宣言し、a には1を、bにはaを代入した。

この時点で printf() した結果は、 ab1 となる。

その後、b5 を代入しなおして、もう一度 printf() すると、a1 のままで、b5 に変更されている。

これは、参照になっていない、つまり、ab は物理的に別物だということ。

ポインタを使ってみる(参照を格納する)

次に参照渡しをやってみる。c を、a のポインタにする。

#include <stdio.h>
int main(){
    int a;
    int *c;

    //  a のポインタ *c の練習。
    a = 1;
    c = &a; //&で参照になる。PHPと似てる。もとい、PHPが似せている。
    printf("a: %d; *c: %d;\n",a,*c);
    *c = 5;
    printf("a: %d; *c: %d;\n",a,*c);

    return 1;
}

これをコンパイルして実行すると、下記の出力が得られる。

a: 1; *c: 1;
a: 5; *c: 5;

2行目の a の値が、先ほどの b の例と結果が変わった。

まず、実装の異なる部分は下記。

  • ポインタ c を宣言するところが、先ほどの b の例と異なる。頭に * を付けて宣言している。
  • cに値を代入するところも異なるようだ。a ではなく、&a を代入している。
  • 結果を出力する部分では、*c というように、*記号を付けている。

これで2行目の a の値が変わったということは、*c = 5; の結果、a5 が代入しなおされたということで、つまり、*ca のポインタとして機能していることになる。

わかりにくいので、もうちょっと分解。

ちょっとこの実験だけだと、何が起きたのかわかりにくいかも知れない。もう少し解剖した実験をしてみると意味わかるかも。

a = 1; の直後に、printf("&a: %d;\n",&a); してみると、次のような出力が得られる。

&a: -1081655872;

値は実行するたびに異なる。これは多分なんだけど、メモリ上のアドレスのようなものを意味した値なんじゃないかと思う。頭に & を付けると、メモリ上のアドレスを返すという規則。この後 &ac に代入されるわけだが、c を出力しても、当然ながら同じ値が得られる。

つまり、c には、アドレスの情報が代入されたということのようだ。そして、c の値が示すアドレスに実際にある値を参照したいときに付けるのが、*記号というわけ。

2重に参照してみる(2重ポインタ)

&* の規則を理解したら、多重ポインタも簡単に理解できる。

ポインタ c のポインタ d を作る実験をやってみる。

#include <stdio.h>
int main(){
    int a;
    int *c;
    int **d;

    //  a のポインタ *c のポインタ **d の練習。
    a = 1;
    c = &a;
    d = &c;
    printf("a: %d; *c: %d; **d: %d;\n",a,*c,**d);
    *c = 3;
    printf("a: %d; *c: %d; **d: %d;\n",a,*c,**d);
    **d = 5;
    printf("a: %d; *c: %d; **d: %d;\n",a,*c,**d);

    return 1;
}

これをコンパイルして実行すると、出力は下記。

a: 1; *c: 1; **d: 1;
a: 3; *c: 3; **d: 3;
a: 5; *c: 5; **d: 5;

*c**d も同じ実体 a を参照しているので、3つとも常に同じ値が出力されている。

この、**d を解剖していくと、理屈はさっきと同じ。

d には c のアドレスが格納されているので、*dc の値である "aのアドレス" ということになる。*da のアドレスなので、 **d は、a の値、というカラクリ。

後は、3重でも4重でも、同じ理屈でアドレスを辿って、元の値を参照することができるわけだ。

まとめ

過去に何度も挑戦してみて、何度も挫折してきたポインタだけど、わかってしまえば難しいことはなさそうだ。
名前領域の管理とか、型のキャストとか、まだわかんない部分は多いけど、もうちょい勉強したら何か作れそうかな?

  • ※この実験は、VMware Player × Ubuntu 8.04 環境で行ったものです。

プロフィール

コヤナギ トモヤ

ウェブ系エンジニアしてます。ウェブデザイナー、ウェブディレクターとしてウェブ制作の仕事に携わり、今はエンジニア職に流れ着きました。誰かのお仕事をちょっとだけ効率化するような支援ツールの開発が好き。オープンソースとMITライセンス大好き。人生後半は自由と民主主義のコントリビューターとして過ごす予定。

ウェブ制作支援ツール Pickles 2 をオープンソースで開発しています。

PHP/JavaScript/NodeJS/nwjs/Laravel/Pickles2/オープンソース/心理学/倫理/自由と民主主義

RSSフィード

ページの先頭へ戻る