前回、「配列はポインタ」だと書きましたが、どうやら・・・、僕の思っていたのと感じが違うようです。とりあえず、無かったこととして忘れて下さい。前にも書きましたが構造体も型を定義し、その型の変数を宣言すると連続したメモリ空間に割り当てられます。「連続な」と言うことは、2つ以上のデータがあると言うことですが、構造体なので2つ以上であるのは当然です。1つならば構造体を使う必要有りませんから。今回も「連続な」がキーワードです。配列のようにインデックス番号こそ有りませんが、連続なメモリ空間が使えるのでアドレスのインクリメント等が使えます。とりあえず、「構造体も連続な空間」を証明しましょう。
っと、その前に、構造体は使えるんでしょうか・・・。んーーー。細かいことを書き出すときりがないし、僕も1通りのやり方しか使っていないので、とりあえず、僕がいつもやっているやり方で宣言します。大丈夫、このやり方でプログラム動いていますから・・・。(^m^;
typedef struct {
これで、TEST_T 型の宣言がプログラムで使えるようになりました。この段階ではまだ、メモリは確保されていません。次の作業でメモリを確保します。
TEST_T test_t;
これで、2(バイト)×5(個)=10(バイト) のメモリを確保したわけです。初めの型を宣言しただけではメモリは確保されません。普通、構造体の定義はヘッダーファイルに宣言し、ソースファイルではそれをインクルードしてその型を用います。プログラムが複数のソースファイルに分かれても、ヘッダーファイルをインクルードしただけでメモリを確保したわけではないので注意して下さい。あくまでも、その型がインクルードしたソースファイルで使えるようになっただけです。
基本はこんなところでいいでしょうか? それでは、実際にソースファイルを使います。サンプルプログラムでは宣言した構造体の先頭アドレスをそれを初期化する関数、dataInit に渡して初期化します。念のため、初期化する関数に渡す前にすべてのメンバにはゼロをセットします。データを表示する関数の引数に指定した構造体のデータの渡し方は表示のみですが、アドレスを渡しています。ご覧あれ・・・。
言い忘れていましたが、ポインタで値を参照する場合、間接演算子であるアスタリスクを使うのと同じように、構造体のポインタではそのメンバを参照するには「->」を使います。使い方はサンプルコードの通りです。
連続な空間である、と言うことに関しては、初期化関数に指定した dataInit 関数が重要です。初めに受け取ったのは構造体のメンバの一つ、 a のアドレスですが、残りのメンバがこれに続く連続なアドレスにメモリを確保しているので、アドレスのインクリメントで次のメンバをポインタが指し示すようになります。わざわざ、アドレスのインクリメントをしているのはその事を知ってもらうためです。
アドレスのインクリメントで不思議に思われた方もおられるかと思いますが、変数を割り当てた以上にアドレスをインクリメントしたらどうなるか、即ち、dataInit 関数の中でもう一度、アドレスのインクリメントしてデータを参照したらどうなるか・・・。答えは、???です。大抵の場合はうまくいきますが、アクセス違反でプログラムがダウンすることもあります。他のプログラムが使用しているメモリ空間をいじることも出来る??、実はこの辺の話しは詳しくないので、何とも言えませんが・・・。OSが管理しているのかな・・・。ま、そんなことにならないよう、くれぐれも気をつけましょう。
変数のスコープに関しては何も話していませんでしたが、多分、なるべく小さくすると言った基本は知っておられると思います。構造体も例外でなく、そのためにアドレスを使って関数から関数にデータを渡していくので、「->」とかは使えるようにしておきましょう。
次回も構造体で配列のメンバを扱います。