【C言語】構造体ポインタを攻略!アロー演算子と現場で必須の「効率化」を解説

【C言語】構造体ポインタを攻略!アロー演算子と現場で必須の「効率化」を解説 C言語

皆さん、こんにちは。リーダーです。

C言語を学んでいて、「構造体」が出てきたあたりまでは「ふんふん、なるほど」と進めていたのに、「ポインタ」と組み合わさった瞬間に頭の中がフリーズしてしまった……なんて経験はありませんか?

実は私も、新人の頃はそうでした。
大手電機メーカーの配属初日に、先輩が書いた「構造体のポインタが縦横無尽に飛び交う通信ドライバのコード」を見せられて、「あ、これ無理だ」と挫折しかけたのを今でも覚えています。

ですが、現場で20年以上泥臭くデバッグを繰り返してきた今なら断言できます。

「構造体ポインタ」こそが、C言語で実用的なプログラムを書くための最大の武器です。
これを使わずに大規模なシステムを作るのは、手漕ぎボートで大平洋を渡るようなものです。

今日は、仕事終わりの一杯を心待ちにしながら、この「構造体ポインタ」の真髄を皆さんの脳髄に叩き込んでいきたいと思います。

【執筆者の簡易プロフィール】
リーダー 執筆者:リーダー
  • 当ブログの統括者兼ライター
  • 45歳男性、既婚、2児の父
  • 組み込みエンジニア
  • 新卒で大手電機メーカーに入社し、開発部門に配属、現在も勤務
  • 主な使用言語はC言語とC++
  • 趣味は毎晩の晩酌

なぜ構造体とポインタをセットで使うのか?「効率」の真実

「構造体をそのまま関数に渡せばいいじゃないか」と思うかもしれません。

確かに、小さな構造体ならそれでも動きます。
しかし、プロの現場では「効率」がすべてです。

例えば、1,000バイトある巨大な構造体を関数に「値渡し」で渡すと、関数を呼ぶたびに1,000バイトのコピーが発生します。
これが1秒間に1万回走ったら……システムのパフォーマンスはガタ落ちです。

一方、ポインタを使えば、渡すのはたったの数バイト(アドレス情報)だけ。

「中身を全部コピーして渡す」のか、「場所だけ教えて見に行ってもらう」のか。
この差が、組み込み機器のようなリソースが限られた環境では生死を分けるんです。

アロー演算子「->」を使いこなせ!ドット演算子との違い

構造体のポインタを扱う際、絶対に避けて通れないのが「アロー演算子(->)」です。
「ポインタが指し示している先の、構造体のメンバ」にアクセスするための記号ですね。

まずは、基本の書き方をコードで見てみましょう。

#include <stdio.h>

typedef struct {
char name[20];
int power;
} Warrior;

int main() {
Warrior w = {"江田島", 9999};
Warrior *p = &w; // 構造体のポインタにアドレスを代入

// アロー演算子を使ったアクセス
printf("名前: %s\n", p->name);
printf("戦闘力: %d\n", p->power);

// (*p).member と書くこともできるが、現場ではアロー演算子が絶対
printf("名前(別の書き方): %s\n", (*p).name);

return 0;
}

このコードのポイントを解説します。

構造体変数 w のアドレスを &w で取得し、ポインタ変数 p に格納しています。
この p を使って中身を書き換えたり参照したりする時、p->name と書くのがアロー演算子の流儀です。

(*p).name という書き方も文法的には正解ですが、カッコが多くて読みづらいですよね。
現場のコードレビューでこれを見かけたら、私は「アロー演算子を使ってスッキリ書こう」とアドバイスします。
読みやすいコードは、バグを減らす第一歩ですから。

関数に構造体を渡す「ポインタ渡し」の流儀

次に、構造体ポインタが最も真価を発揮する「関数との連携」を見ていきましょう。

構造体を直接渡すのではなく、ポインタを渡すことで「関数内で元の構造体を書き換える」ことが可能になります。

#include <stdio.h>

typedef struct {
int hp;
int mp;
} Status;

// 構造体のポインタを引数に取る関数
void level_up(Status *s) {
s->hp += 10;
s->mp += 5;
printf("レベルアップ! HP:%d MP:%d\n", s->hp, s->mp);
}

int main() {
Status my_status = {50, 20};

// アドレスを渡す(ポインタ渡し)
level_up(&my_status);

// main関数側の変数も書き換わっている
printf("最終ステータス HP:%d\n", my_status.hp);

return 0;
}

解説します。

level_up 関数の中で、アロー演算子を使って値を増やしています。
もしこれがポインタ渡しではなく「値渡し」だったら、関数の中でいくらHPを増やしても、main 関数に戻った瞬間にその努力は水の泡になります。
コピーに対して操作をしても、オリジナルは変わらないからです。

「関数の外にあるデータを効率よく、確実に更新したい」

この意図がある時は、必ず構造体ポインタを使うべきです。
これは組み込みエンジニアとしての鉄則です。

mallocによる構造体の動的確保とNULLチェック

最後に、一歩進んだテクニックとして malloc 関数を使った動的メモリ確保についても触れておきます。
実行時に必要な分だけメモリを確保する、非常に強力な手法です。

#include <stdio.h>
#include <stdlib.h> // malloc, freeに必要

typedef struct {
int id;
} Data;

int main() {
// 構造体1個分のメモリをヒープ領域から確保
Data *p = (Data *)malloc(sizeof(Data));

// 【重要】現場で必須のNULLチェック!
if (p == NULL) {
    printf("メモリ確保に失敗しました\n");
    return 1;
}

p->id = 101;
printf("確保されたID: %d\n", p->id);

// 使い終わったら必ず解放する
free(p);
p = NULL; // 安全のためにNULLで初期化

return 0;
}

malloc(sizeof(Data)) で、メモリの海から構造体1個分だけのスペースを借りてきます。

ここで初心者が最も忘れがちなのが、「NULLチェック」です。

メモリは無限ではありません。
もし確保に失敗したのに p->id にアクセスしようとすれば、プログラムは「セグメンテーション違反(Segmentation Fault)」で無残にクラッシュします。

「動的にメモリを確保したら、必ずNULLかどうかを確認する」

この1行を書くか書かないかで、エンジニアとしての信頼度が決まると言っても過言ではありませんよ。

まとめ|構造体ポインタは「プロへの階段」

C言語での構造体ポインタ、いかがでしたか?

ポインタを使うことで「コピーの無駄」を省き、爆速なコードが書ける。
アロー演算子(->)は「ポインタの先の中身」を指す魔法の記号。
関数にアドレスを渡せば、オリジナルを自在に操作できる。
malloc を使う時は「NULLチェック」を絶対に忘れない。

この概念が自分のものになれば、OSのソースコードやライブラリの内部構造も驚くほど読みやすくなります。
最初は難しく感じるかもしれませんが、手を動かしてコードを書けば、必ず手が覚えてくれます。

さて、今日の講義はここまで。
私もこれから「キンキンに冷えた麦のジュース」をポインタで指し示して、喉に「値渡し」してくるとしますよ。

以上、リーダーでした。

コメント

タイトルとURLをコピーしました