C言語の構造体のポインタの使い方
目次
- C言語の構造体のポインタの使い方
- 構造体とは?
- ポインタとは?
- 構造体のポインタのメリット・デメリット
- 構造体のポインタ変数の宣言方法
- 構造体のポインタ変数の定義方法
- 構造体のメンバをポインタで参照する方法
- 構造体のポインタを引数に取る関数
- 構造体のポインタを返す関数
- constと構造体のポインタ
- おわりに
C言語の構造体のポインタの使い方
C言語では構造体を扱うことができます。
もちろん構造体のポインタも使うことができます。
この記事ではC言語の構造体のポインタについて具体的に解説します。
C言語では構造体はポインタで扱うことが非常に多いです。
その方がメモリが省エネになり、速度も上がるからです。
C言語で構造体のポインタを扱えるようになるのは非常に有意義と言えます。
構造体のポインタは扱いかたがわかると簡単に扱うことができます。
それではC言語の構造体のポインタについて解説していきます。
構造体とは?
まずおさらいとして構造体とはなんなのか? というところから解説します。
構造体とは複数の変数をまとめた構造のことを言います。
たとえばAnimal
という構造体があったとして、この構造体にeyes
やweight
などの変数を持たせます。
構造体からは構造体変数を定義することができます。
この構造体変数をアドレス演算子で参照すると、構造体変数のポインタが取り出せます。
たとえば先ほどの例のAnimal
という構造体を宣言する場合は↓のようにします。
struct Animal { int eyes; double weight; };
構造体変数を定義する場合はたとえば↓のようにします。
struct Animal animal = { 2, 54.3 };
↑の場合、struct Animal
が構造体でanimal
が構造体変数です。
animal
のeyes
には2
, weight
には54.3
がセットされています。
ポインタとは?
ポインタもおさらいしておきます。
ポインタとは、変数や関数へのショートカットのことを言います。
ポインタを使うと変数や関数に間接的にアクセスすることができます。
ポインタ(アドレス)を格納する変数のことをポインタ変数と言います。
構造体でポインタを利用したい場合は、構造体変数にアドレス演算子を使うか、構造体のポインタ変数を使います。
ポインタ変数の宣言にはアスタリスク(*
)を使います。
また、ポインタ変数の実体化(間接的な値の参照)でもアスタリスクが使われます。
構造体のポインタではこのアスタリスクに加えて、アロー演算子という演算子も使うことができます。
ポインタは変数や関数へのショートカットで、構造体の場合は構造体へのショートカットになります。
構造体のポインタのメリット・デメリット
構造体のポインタのメリット、デメリットはなんでしょうか?
構造体のポインタを使うことで得られる利点は?
また、気をつけたい点は?
これはまとめると↓になります。
ポインタを使えば省コストでメンバにアクセスできる
セグフォになる可能性がある
ポインタを使えば省コストでメンバにアクセスできる
メリットは、構造体のポインタを使えば省コストで構造体のメンバにアクセスできることです。
構造体のメンバのアクセスは、関連する処理によってコストがかかる場合があります。
たとえば関数に構造体変数を渡す場合を考えます。
無数のメンバを持つ構造体の構造体変数を関数の引数に渡すと、コピーが発生します。
そのコピーは構造体のメンバが多いほどコストが増えます。
しかし関数の引数に構造体のポインタを渡すようにした場合、そのコピーのコストはなくなります。
結果的に関数内で構造体のメンバにアクセスするときも、省コストでアクセスすることが出来るということになります。
このようにC言語の構造体のポインタを使えばコピーのコストを押さえてメンバにアクセスすることが出来ます。
関数と構造体を多用するとき、構造体のポインタは切っても切れない関係になります。
セグフォになる可能性がある
いっぽうデメリットは、構造体のポインタを使うとセグフォになってプログラムがクラッシュする場合があるということです。
これはポインタの特性によるものです。
ポインタを使う以上、セグフォになる可能性は捨てきれません。
これはポインタにNULLポインタや不正なアドレスが入っている場合があるからです。
構造体のポインタにおいてもこれはかわりません。
このようにC言語の構造体のポインタを使うと、セグフォになる可能性があります。
ですので注意が必要です。
構造体のポインタ変数の宣言方法
構造体のポインタ変数の宣言方法は↓になります。
構造体名 *構造体のポインタ変数名;
これを実際にコードにすると↓のようになります。
struct Animal { int eyes; double weight; }; int main(void) { struct Animal *animal; // ポインタ変数の宣言 return 0; }
構造体のポインタはこのように普通の変数のポインタと同じように宣言することができます。
宣言だけをした場合は構造体のポインタ変数にはデタラメな値(環境によって変わる)が入っています。
そのため宣言をしたら初期化をするか、あるいは宣言と同時に初期化するようにしたほうがいいです。
玄人のC言語使いは未初期化のポインタ変数を長いスパンで使うこともあります。
(^ _ ^) | 玄人のC言語使いはどこに生息しているの? |
(・ v ・) | あまり表には出てこないよ |
(ー o ー) | レア・モンスターみたいだな |
構造体のポインタ変数の定義方法
構造体のポインタ変数を定義する場合は↓のようにします。
構造体名 *構造体のポインタ変数名 = 値;
宣言と定義の違いは実際に値が入っているか否かの違いです。
コードにすると↓のようになります。
#include <stdio.h> struct Animal { int eyes; double weight; }; int main(void) { struct Animal *animal = NULL; // ポインタ変数の定義 return 0; }
↑のコードの場合、構造体Animal
のポインタ変数animal
にはNULLポインタが代入されています。
このようにポインタ変数をNULLポインタで定義するのは一般的でよく行われます。
とくに複雑なシビアな処理においては、バグを減らすためにこういった定義が行われることがあります。
また、構造体変数のアドレスを構造体のポインタ変数に代入しておく場合は↓のようにします。
struct Animal { int eyes; double weight; }; int main(void) { struct Animal animal = {0}; struct Animal *panimal = &animal; // animalのアドレスを代入 return 0; }
↑の場合、Animal
の構造体変数はanimal
, Animal
のポインタ変数はpanimal
です。
さきにanimal
を定義し、その後にpanimal
にanimal
のアドレスを代入しています。
このようにすると構造体のポインタ変数を宣言と同時に他の変数のアドレスで初期化することができます。
構造体のメンバをポインタで参照する方法
構造体のメンバ変数にアクセスする方法どうしたらいいのでしょうか?
構造体のメンバ変数にアクセスする場合は、ドット演算子を使うのが普通でした。
ポインタ変数を使う場合はドット演算子の他にアロー演算子を使うことができます。
アロー演算子でメンバを参照する
ポインタ変数で構造体のメンバをアロー演算子で参照し、printf()
で出力します。
#include <stdio.h> struct Animal { int eyes; double weight; }; int main(void) { struct Animal animal = { 2, 54.3 }; struct Animal *panimal = &animal; printf("eyes[%d] weight[%f]\n", panimal->eyes, panimal->weight); // eyes[2] weight[54.300000] return 0; }
↑の場合、Animal
構造体のポインタ変数はpanimal
です。
panimal
にはAnimal
構造体の構造体変数animal
のアドレスが入っています。
panimal->eyes
とやってアロー(->
)を伸ばすと、間接的に構造体のポインタが指す実体のメンバにアクセスできます。
この例で言うと、panimal
からanimal
のメンバeyes
を参照していことになります。
weight
についても同様です。
このようにアロー演算子を使うと、構造体のポインタから実体のメンバにアクセスすることができます。
アロー演算子でメンバに代入する
ポインタ変数で構造体のメンバを参照して、メンバに値を代入します。
#include <stdio.h> struct Animal { int eyes; double weight; }; int main(void) { struct Animal animal = { 2, 54.3 }; struct Animal *panimal = &animal; panimal->eyes = 4; panimal->weight = 123.4; printf("eyes[%d] weight[%f]\n", panimal->eyes, panimal->weight); // eyes[4] weight[123.400000] return 0; }
↑の場合、Animal
構造体の構造体変数animal
のメンバはeyes
が2
, weight
が54.3
で初期化されています。
その後にポインタ変数panimal
を通してアロー演算子でメンバに値を代入しています。
結果は代入した値が出力されます。
構造体のポインタを引数に取る関数
構造体のポインタを引数に取る関数を作りたい場合は↓のように関数を書きます。
#include <stdio.h> struct Animal { int eyes; double weight; }; // 構造体のポインタ変数を引数に取る関数 void func(struct Animal *panimal) { printf("eyes[%d]\n", panimal->eyes); printf("weight[%f]\n", panimal->weight); } int main(void) { struct Animal animal = { 2, 54.3 }; func(&animal); // アドレス(ポインタ)を渡す // eyes[2] // weight[54.300000] return 0; }
構造体のポインタを関数で受け取る場合は、関数内で引数がNULLポインタかどうかチェックする場合があります。
セキュアなコードを書きたい場合は、そのようにチェックをした方がバグは減らせます。
その分手間は増えるのですが。
しかしそれでも不正なアドレスがポインタ変数に入っていた場合はセグフォなどになる可能性があります。
怖いのはセグフォにならずにプログラムが走ってしまうケースですが、このケースはバグの修正が困難になります。
構造体のポインタを返す関数
構造体のポインタを返す関数は↓のように書くことができます。
#include <stdio.h> struct Animal { int eyes; double weight; }; // 構造体のポインタを返す関数 struct Animal *func(struct Animal *panimal) { return panimal; } int main(void) { struct Animal animal = { 2, 54.3 }; struct Animal *panimal = func(&animal); // アドレス(ポインタ)を渡す return 0; }
構造体のポインタを関数から返す場合、ローカル変数のポインタ変数を返すのはご法度です。
static
にして静的なメモリに確保している場合は別です。
ただそれでもスレッドセーフの問題などがあります。
関数内の構造体の変数は関数内で寿命が尽きます。
そのため、その変数のポインタを返しても意味がありません。
↑のコードの場合は関数の呼び出し時に渡している構造体変数animal
のポインタをそのまま関数から返しています。
この場合はポインタの指し示す変数の寿命はmain
関数内になるので安全と言えます。
ただこの関数に意味があるかといわれるとあまり意味はありません。
しかしC言語では関数が失敗したかどうかを表すのに構造体のポインタを返す場合があります。
NULLでなければ成功、NULLであれば失敗、という風にです。
そういう場合は↑のコードのように引数のポインタをそのままreturn
する時があります。
constと構造体のポインタ
普通の構造体変数と同じように、構造体のポインタ変数にconst
を付ければメンバを変更不能にできます。
struct Animal { int eyes; double weight; }; int main(void) { struct Animal animal = {0}; const struct Animal *panimal = &animal; // animalのアドレスを代入 panimal->eyes = 4; // error! return 0; }
構造体のポインタ変数とconst
は相性が良いので積極的に使っていきたいところです。
const
にしてメンバを保護すれば意図しない代入などを防ぐことができてバグを減らせます。
ただその分、設計の難易度は上がります。
const
をまったく使わないというプロジェクトもあります。
プログラミング言語がそもそもconst
をサポートしていないケースです。
そういった場合もほとんどの場合は問題になることはないようです。
ただ開発者のスキルが必要とされるので、チームの開発者のスキルにむらがある時はconst
を使った設計をしたほうがいいかもしれません。
おわりに
今回はC言語の構造体のポインタを解説しました。
構造体のポインタを使えるようになると構造体を使うのが楽しくなってきます。
ぜひ押さえておきたい技術と言えます。
(^ _ ^) | 構造体を間接的に・・・ |
(・ v ・) | 間接参照 |