C言語のポインタの勉強方法1

C言語を勉強すると,ほとんどの人は,いくつかの概念が理解できず,つまずく。ポインタはその中でも最大のもの。

ポインタに限らず,C言語を勉強している人で,なかなか理解できない場合は,「あなたのイメージとは全然別のものである」という可能性があることに注意しよう。

「たぶん○○なんだなぁ」では,だめである。その○○は,ほぼ間違っている。

ちゃんと理解したいならば,勝手な想像や先入観を一切捨てて考えたほうが理解が早い。

なぜなら,それは,あなたが今まで経験したことのない「概念」である可能性が高い。

それを,今まで経験した中の概念に結びつけようとしている可能性がある。

だから,なかなか理解できないのだ。




さらに悪いことに,ポインタは中途半端な理解で使うと非常に危険なのだ。

あまり理解していない場合は出来る限り使わないようにしよう。


しかし,実際のプログラマの仕事は若手新入社員など経験の浅い人がやる場合が多い。しかし,経験の浅いプログラマの中には,当然,ポインタをあまり理解していないひとが多い。


新卒の若手プログラマでC言語でプログラムを作成させるならば,注意しないと,不安定なシステムになってしまい,デバッグが大変なばかりか,完成後に深刻な問題を引きおこしたりしかねない。


ポインタは,通常,ほとんどのプログラミング言語にはない。

なぜC言語にはあるのかというと,アセンブラで書かれていたUNIXというOSをメンテナンスや移植をしやすいようにするために,新たに作られたプログラミング言語だ。アセンブラの代わりなので,コンパクトな言語仕様とアセンブラにある強力なメモリ参照機能を持たせたいということで,ポインタを持っている。UNIXは,ハードウエアに依存する部分は,アセンブラで書いて,大半の依存しない部分はC言語で記述されている。

C言語は「システム記述言語」といわれている。

C言語が良く使われる理由は,巨大なOSを記述できる能力があり,コンパクトで高速に動作という点が大きいだろう。そのかわり,学習には向かない,プロ向きの言語とも言える。


ポインタを使わなくてもある程度のプログラムは記述できる。

逆に言うと,ある程度を超えれば,ポインタを使わないと記述できないものがある。

ポインタの悪い点としては,通常のポインタの表記以外で,自動的にポインタ扱いになるため,知らないうちにポインタを使って,トラブルにはまることがある。

たとえば,よくあるのが,関数の引数に配列を渡した場合だ。具体的には,以下の様なコードだ。

#include <stdio.h>

void fn(char c[10])
{
  printf("%s\n", c);
}
int main( )
{
  char c[10] = "123456";
  fn(c);

  return 0;
}

void fn(char c[10]) の cは どう見ても 要素数が10個のchar型の配列である。しかし,10という数字は,完全に無視されている。そもそも,これは配列ではなくて,char型のポインタ変数だ。

つまり,void fn(char *c) と書いても完全に同じなのだ。また,void fn(char c[ ])と書いても同じなのだ。

10ってのは,書きたければ書いてもいい。但し,多次元配列の場合は,最後の要素番号以外は書かないといけないという,例外付だ。

例:
void fn2(char v[10][10])は,void fn2(char v[ ][ ])とはかけない。 void fn2(char v[10][ ]) という風に,最後の要素番号のみ,省略可能(そもそも意味がない)。

3次元ならば,void fn3(char w[10][10][10])は, void fn3(char w[10][10][ ]) とは書けるが,void fn3(char w[ ][ ][ ]) とか,void fn3(char w[10][ ][ ]) とか,void fn3(char w[10][ ][10]) とか,とか,void fn3(char w[ ][10][10]) はだめだ。


また,配列には複数の用途があり,それぞれの違いを理解しなければならない。

1.関数の戻り値に複数の値を使いたい場合,引数にポインタ型変数を使う
2.メモリの動的確保で確保したメモリへの管理・アクセスのため
3.配列の代わりに使う。(配列とまったく同じ書式が使える)
4.配列の中の特定の要素を指すのに使う
5.別の変数を参照するのに使う

ほかにもあるかも。とりあえずこれだけ。


ポインタ変数と普通の変数の違い

普通の変数は,値を1つ記憶しておける。計算結果の格納などが出来る。

ポインタ変数は,間接的に変数を扱うもの。通常の変数は直接アクセスできるが,ポインタで数値を扱う場合は必ず間接的に操作することになる。

通常の変数(直接アクセス)
[C言語プログラム] は,[変数]を見に行く。場所はあらかじめ分かっている。

ポインタ変数(間接的アクセス)
[C言語プログラム] は [ポインタ変数]を見に行く。ここに,場所情報が入っている。この場所情報を使って実際の変数などににアクセスしにいく。

変数の場所の取得方法は, & を頭につける。
int a;
int *p;

p = &a;

この,&a とは,変数aの場所情報を変数pに代入している。変数pはポインタ変数なので,場所情報を記憶することが出来る。

*p = 10;

とすれば,変数pの場所情報(現在は変数a)の場所にアクセスし,値10を代入する。

p = 10;

ではない。これは,変数pに値10を設定しようとしているが,pはポインタ変数なので,記憶できるのは場所情報だけだ。10は場所情報(アドレス)ではなくて,ただの整数値なので,代入できない。

*を頭につけることで,変数pに入っている場所情報を使って,その場所にアクセスすることが出来る。


関数の引数にした場合
void fc(int *p);
int main( )
{
  int v;
  v = 20;
  fc(&v);
  return 0;
}

void fc(int* p)
{
  *p = 10;
}

という表現が分かりにくいかもしれない。

関数の引数の受け渡しは以下の様な解釈だ

void fx(int x);
int main( )
{
  int y;
  y = 20;
  fc(y);
  return 0;
}

void fc(int x)
{
  x = 30;
}

は,関数を使わずに表現すると,以下の様なものになる。

int main( )
{
  int y;
  int x;

  x = y; /* fc(y);の関数呼び出し時の引数の内部動作と同じ。yの値をxに代入。関数呼び出しのとき,引数へは全て別の変数への代入で行われている */
  x = 30;   /* 別の変数なので関数の中で引数の変数の値xを書き換えても,もとの変数yは,当然影響を受けない。 */

  return 0;
}

上の2つの関数を使った場合と,使わない場合の動作の違いはほとんどない。

これを関数の引数がポインタ変数の場合に書き換えると以下の通りだ。


int main( )
{
  int v;
  int *p;
  v = 20;

  p = &v; /* fc(&v); の関数呼び出しの引数の内部動作と同じです */
  *p = 10;  /* *p は当然,vの値を書き換えることが出来ます。 */
  return 0;
}

と,いう感じになります。



この記事へのコメント

この記事へのトラックバック