ポインタ

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言語 配列とポインタどっちを使う?

配列は、ポインタを使ってデータを取得することができます。逆にポインタに対して、配列でアクセスしようと思えばできます。

では、配列とポインタのどっちを使うほうがいいのか?ということになりますが、ややこしくなくて済むのは配列でしょう。

しかし、関数には配列を渡したり、配列を戻り値にすることはできませんから、ポインタを使うことになるでしょう。

ここでは、私が思う配列とポインタのどっちを使ったほうがいいかを書いていこうと思います。

続きを読む

C言語 組み込みマイコンでポインタをよく使う3つの場面

C言語で一番難解なポインタを使用する場面は、いろいろありますが、
ここでは、組み込みマイコンでよく使用する場面を3つ紹介したいと思います。

・ハードウェアの設定や入出力を扱うとき
・ハードウェア依存部分とアプリケーション部分とのやり取り
・2つ以上の戻り値を持つ関数を作るとき

順に解説していきます。

続きを読む

C言語 ポインタの使い方でこれだけは知っておきたい3つのこと

C言語をやっていくうえで、避けて通れないのに、一番ややこしいのがポインタではないでしょうか?

ポインタとは、変数のアドレスを格納している変数のことを言います。
アドレスとは、各変数に割り当てられたメモリの番地です。

と言ってもイメージできないですよね。私もC言語を始めた当初は何のこっちゃ
さっぱりわかりませんでした。少なくとも学生時代は理解できていませんでした。

今はいいと思いますよ。できているプログラムをコピペして、実際に動作をさせてみて、その流れを把握していくようにすれば、イメージできて来るとはずです。

私自身もとにかく、コピペして、デバッガで動かしてみたり、printf文とかで目に見える形で表示させてみたりして、そのプログラムがどう動いているかを確認しながら、少しずつ触ってみて、わかるようになりました。

したがって、あなたも徐々にわかるようになればいいのです。始めから、何でもできる人は天才以外に存在しません。

今はとにかく、次に解説することだけを最低限、覚えてもらえれば、それなりにプログラムが組めるようになります。

続きを読む