意外と気づかない間違い

C言語 ポインタの4つの落とし穴とは?

C言語では、ポインタが一番ややこしくて、重要であることはご存じかと思います。

ポインタの扱いを間違えると、プログラムが異常終了したり、暴走したりする可能性があります。

ここでは、ポインタの落とし穴について、お話ししていきます。

NULLポインタへ代入し、プログラムが異常終了

NULLポインタはどこも指し示さないポインタ定数です。ほとんどの処理系では “0” で定義されています。

このNULLポインタを介した代入を行ってしまうと、プログラムは、ほぼ確実に異常終了します。

なぜなら、メモリへの不正なアクセスが行われると、システムがエラーを起こすからです。

NULLポインタはどこも指し示していないポインタなので、それに対して、操作することをやってはいけません。

このようなことです。

int *pt = NULL;
*pt = 10;

ポインタ変数ptはNULLになっているのは、おわかりですね。したがって、「*pt = 10」とすることはできません。

ポインタ変数ptをNULLで初期化するのは、当たり前に行います。そこから、使用するためには、有効なポインタを設定する必要があるのですね。

このようなプログラムでは、間違いがすぐにわかるでしょう。

ほとんどの場合は、複雑に入り組んだプログラムの内部で、たまにポインタがNULLになっていることがあり、そこに代入してしまうことで起こるのです。

たまにしか起こらないため、プログラムの間違いを見つけるのは、かなり厳しいでしょう。デバッガを使って、異常が起きたときに、変数の状態を見ていくしかないと思います。

不正な領域を指している

ポインタがNULLでなくても、不正な領域をさしているときも、そのポインタを使って代入してはいけません。

例えば、次のような関数を見てみましょう。

char *get_address(void)
{
    char address[100];

    printf("input address : ");
    scanf("%s", address);

    return address;
}

住所データを入力し、そのデータを取得しようとする関数です。

しかし、これでは、入力された住所データは、関数の外では使用することができません。なぜだか、わかりますか?

配列addressは、関数内で宣言されています。この配列は、関数を抜けるときに、確保された領域が開放されてしまうからです。

そうならないようにするには、住所データの配列の宣言を、関数の外で行って、引数でポインタ渡しをするか、staticをつけて、領域を開放されないようにするかです。

char *get_address(void)
{
    static char address[100];

    printf("input address : ");
    scanf("%s", address);

    return address;
}

このようにすれば、関数の外でも住所データが参照できます。ただし、関数をコールするたびに、配列addressのデータは、上書きされてしまうことに注意してください。

内容とアドレスを混同する

ポインタが、ややこしいところになっているのは、やはり、ポインタの値と、ポインタが指している先を混同してしまうからだと思います。

頭の中ではわかっているのに、C言語の表記方法とリンクできていないのではないでしょうか?

次のように宣言されている変数を見てましょう。

int a;
int* ap;

この2つの変数の型は、「*」がつくかつかないかだけで、見た目は似ていますが、全く異なるものです。「*」が1つ増えたり減ったりするだけで、大きな違いがあります。

変数aには、整数値を代入することができます。

a = 100; /* 正しい代入 */

しかし、ポインタ変数apには、整数値を代入できません。

ap = 100; /* 間違った代入 */

ポインタ変数apは、整数型の変数のアドレスを代入することができます。

ap = &a; /* 正しい代入 */

これで、ポインタ変数apは変数aを指していることになります。したがって、apとaは常に同じ値になります。つまり、aの値を変えても、apの値をかえても、aの値は変化します。

配列とポインタを混同する

あるファイルで、配列が次のように定義されているとします。

int a[100];

これを別ファイルから、この配列を参照しようとしたとき、次のように宣言したとします。

extern int* a;

残念ながら、この宣言は間違いです。ほかのファイルで配列として宣言されているaを、ポインタとして宣言してはダメなのです。

正しくは、

extern int a[];

と宣言します。

間違った方の宣言は、aというポインタ変数がどこかで定義されているということなのですが、配列のaは配列の名前という定数なので、externを使って、変数と見なすことができないのです。

これはポインタと配列を混同した例になります。

たしかに、配列名をポインタ変数に代入することは可能です。

int a[100];
int* pa = a;

しかし、aという定数の値を変数paに代入しているだけなので、aとpaを同じものとしているわけではないのです。

まとめ

ここでは、ポインタの落とし穴について、お話しました。

やっぱり、ややこしいですよね?

ポインタを使うのを止めとこうと思いますよね?

しかし、ポインタはややこしいですが、便利なのは事実です。少しずつでいいので、使いこなせるようになっていきましょう。

C言語 while文の「=」忘れはif文の「=」忘れより致命的になる

同じ処理を繰り返して、行いたい場合、while文、for文を使用することがほとんどでしょう。

しかし、気を付けてほしいのは、C言語におけるwhile文は実行条件が変数同士が等しいときという場合、「=」を一つ書き忘れると、if文の書き忘れより、はるかに致命傷になります。

if文の「=」書き忘れについては、こちらの記事をどうぞ。

C言語 if文の「=」忘れは致命的になる

続きを読む

C言語 if文の「=」忘れは致命的になる

C言語に限らず、プログラミングをしていくうえで、条件文はかかせないでしょう。

特に、C言語における条件文ifで、はまると大変な状況になるので、注意しましょう。

では、どう大変なのか見ていきます。

続きを読む

C言語プログラミングで陥ってしまう単位変換による割り算のワナ

意外と陥ってしまう単位変換の失敗で、思っている値と異なってしまうことがあります。

先日も、後輩からうまくいかないんです。と相談を受けて一緒に原因を探っていました。

結局、単位変換のところが悪くて、思っていた結果と違っていたということでしたが、これを解決するのに2時間もかかってしまいました。

続きを読む