C言語プログラミングの中でも「最も有用」だが「最も悪名高い」『ポインタ』 – 使用例とメリット/デメリット

ポインタを使わない例

名簿 PROFILE table から i 番目の情報を取り出すプログラムを例題にしましょう。

typedef struct stProfile
{
    int number;
    char name[40];
    int age;
}PROFILE;
PROFILE table[100];

まずは、ポインタを使わない場合のプログラムなら、こんな感じでしょうか。

int get_profile1(int i)
{
    return table[i].number;
}

int get_profile2(int i)
{
    return table[i].age;
}

void print_profile(int i)
{
    printf("number=%d, age=%d\n", get_profile1(i), get_profile2(i));
}

2つくらいなら、これでも構わないでしょう。しかし、数が増えると面倒です。

今後数が増えるかも知れない、という話なら、ちょっと困ります。

nameも出そうとすると、もうポインタ使ったほうが早いですね。

ポインタを用いた例

PROFILE *get_profile(int i)
{
    return &table[i];
}

void print_profile(int i)
{
    PROFILE *p = get_profile(i);
    printf("number=%d, age=%d, name=%s\n", p->number, p->age, p->name);
}

ポインタを使うと、構造体や配列等をまとめて一言で受け渡しができるのです。

これは、項目が増えれば増えるほど、複雑になればなるほど、効果がある、ということです。

Java等のオブジェクト指向プログラム言語では、こういった一塊のモノをオブジェクトと呼びます。
オブジェクト指向言語の場合は、安全にモノの受け渡しができますが、その分、処理量が多く、重くなります。
ソース上はシンプルでも、それをコンパイルしたものは大きくなります。

その点、C言語のポインタは、モノの場所を伝えるだけなので、処理が増えることも重くなることもありません。

次の例の様に、ポインタでバッファを渡して、書いてもらう、ということもできます。

void get_profile_w(int i, PROFILE *p)
{
    *p = table[i];
}

void print_profile(int i)
{
    PROFILE p;
    get_profile_w(i, &p);
    printf("number=%d, age=%d, name=%s\n", p.number, p.age, p.name);
}

ポインタのデメリット – 何が危険なのか

さて、ポインタを使う上での問題は…例えば、
i が table のサイズを超えていたら、どうなるでしょう?

そう、アウトです。ソフトが停止するかも知れないし、変な動作をするかもしれないし、
最悪、何も起こらないが、後で、他の所の動作がおかしくなる、
…なんて事が
普通に起こるのです。

悪名も立ちますね。

i が100またはそれ以上になると、用意されているメモリの範囲をはみ出します。

このプログラムがLinuxのアプリやWindowsのアプリであった場合は、メモリの共有違反だとか、オーバーフローだとかのエラーが報告され、そのアプリがダウンするだけで済むかもしれません。

それは、OS等の土台となるソフトウェアが裏で稼働しており、そのフォローの上でアプリが動作しているため、アプリの挙動が致命的な障害に陥っても、CPUが停止するような最悪の状態になる前に、OSが直前でアプリの動作を止めて、救ってくれているためです。

しかし、OSやドライバの場合はどうでしょう。あるいは、OSにそのフォローが備わっていない環境で動作するアプリの場合はどうでしょう。

OSやドライバは、自身に致命的な問題が発生した場合は、誰も助けてくれません。
ハイパフォーマンスを追及して便利機能を犠牲にしたファームウエア等のプログラムは、フォローの機能自体が実装さえれていません。

そうなると、CPU停止、あるいは再起動、つまりハングアップ(操作不能)に至ります。