わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

構造体プログラミングの実例(番外編)〜少し手直し

3日置いてソースコードを見直しまして,学生ほかにこのプログラミングスタイルを真似てもらうとすると,いくつかよくない記述をしていたのに気づきました.

  1. 無名構造体に対してtypedefしている.
  2. 関数名について,「initialize」をはじめ,複数の構造体を使用する場合に対応できていない.
  3. mallocしたのにfreeがない.

これらはいずれも,このプログラムのみをコンパイルして実行する分には,問題がないのですが,エントリは「情報教育」,言い換えるとプログラミング授業を支援するために書いている内容ですので,教育的配慮として,フォローしておきます.

1. 無名構造体に対してtypedefしている.

構造体を定義する際,typedefがよく使われます.というのも,typedefなしに

struct rrtable {
  char player[PLAYER_MAX + 1];
  int table[PLAYER_MAX][PLAYER_MAX];
  int player_number;
};

と書くと,定義される型はrrtableではなく,struct rrtableだからです.関数の引数で,毎回struct rrtableと書くのは面倒なものです.言ってみれば「struct」の語をソースコードから減らすために*1,typedefで型名をつけるわけです.
無名構造体が気持ち悪ければ,構造体型とその別名を同時にtogether定義しましょう.

typedef struct rrtable {
  char player[PLAYER_MAX + 1];
  int table[PLAYER_MAX][PLAYER_MAX];
  int player_number;
} rrtable;

こうすると,互換性のある2つの型,「struct rrtable型」と「rrtable型」が定義されます.structを書いても書かなくても,rrtableは同じ型です.
とはいえ,rrtableという単語が両方に出てくるのもまた不気味です*2.ということで,次のように定義することもあります.

typedef struct RRTABLE {
  char player[PLAYER_MAX + 1];
  int table[PLAYER_MAX][PLAYER_MAX];
  int player_number;
} rrtable;

このとき,定義されるのは「struct RRTABLE型」と「rrtable型」です.いずれにせよ,「struct 〜型」は形式的なものです.

2. 関数名について,「initialize」をはじめ,構造体を複数定義して使用する場合に対応できていない.

複数のプログラムで,構造体オブジェクトを作るには…それぞれ異なる関数名にしないといけません*3.今回のプログラムでは,initializeのみが,そういう「他とバッティングする」おそれのある関数ですが,一般には,何らかのデータ構造(たいていは構造体)をアクセスする関数群を定義したら,その関数名の先頭や末尾に,それとわかる名前をつけます.
関数プロトタイプを使って説明しましょう.rrtableを含む関数には,

rrtable *initialize(char *player);
void print_table(rrtable *tab);
int search_player_index(rrtable *tab, char c);
void add_result(rrtable *tab, char *line);

がありますが,これらの関数名に「rrt_」といった接頭語を独自に加えて

rrtable *rrt_initialize(char *player);
void rrt_print_table(rrtable *tab);
int rrt_search_player_index(rrtable *tab, char c);
void rrt_add_result(rrtable *tab, char *line);

とすれば,混乱が減るということです.もちろん関数定義や呼び出しのところも変更しておきます.
それにしてもこの命名法,関数名の最初と(rrt_initializeを除く)第1引数の2箇所に,「この関数はrrtable型に対する処理をしますよ」と宣言しているわけで,無駄を感じます.この無駄は,オブジェクト指向により解決できます.逆に,「構造体 + 手続き型」でプログラムを書くときの問題点を克服するものとして,オブジェクト指向プログラミングを勉強するのもいいでしょう.

3. mallocしたのにfreeがない.

malloc-free論争というのが,1990年代後半に起こりました.私も当時はNetNewsをよく読んでいたものです.
結局のところ,「プログラム終了時に,freeをしなくてもかまわない」という認識でよさそうです(参考: 1, 2, google:malloc+free:title).
まあ,「一連の関数群」を定義するとなると,mallocで確保した領域を開放する処理もあるべきと思ったりしますので,簡単に,開放用の関数も書いておきます.

void clear(rrtable *tab)
{
  free(tab);
}

呼び出すには,main関数の「return 0;」の直前に,「clear(tab);」と書けばいいでしょう.

*1:字数が減るほかに,構造体を意識せずにコードが書けるというメリットもあります.

*2:文法としては,「構造体のタグ」と「型名」とは別々に管理されるので,問題ありません.

*3:関数名にstaticをつけて,別々のファイルで管理するのではうまくいきませんね.呼び出すときにどの関数を使えばいいか,という問題が解決できていませんから.