Hatena::ブログ(Diary)

とくに、ないです。 RSSフィード

2009-08-08

.cファイルをインクルードしちゃいけない理由

小川氏の疑問に(上から目線で)答えます。

「何のこっちゃ」って人はまず http://ogawa-sankinkoutai.seesaa.net/article/125213376.html を見てください。

私の予想

「.hファイルには宣言を書き.cファイルには定義を書く」という暗黙の了解と「宣言は(矛盾しない限り)複数記述してもいいが定義は単数で無ければいけない」というルールが影響していると思います。

どういう事さ?

例えば、 hoge.c というファイルで

void hoge_func()
{
	/* 適当にアレする */
}

と関数を定義している場合、おそらく hoge.h というファイルでは

void hoge_func();

と宣言されていると思います。

そして foo.c で

#include "hoge.h"
void foo_func()
{
	hoge_func();
}
#include "hoge.c"

と記述されている。ここまでが小川氏が書いた話。(1行目のインクルードは俺の想像ですが)

ここまではコンパイルもリンクも通ります。

ところが bar.c で

#include "hoge.h"
void bar_func()
{
	hoge_func();
}
#include "hoge.c"

とするとエラーになると思います。

おそらくコンパイルエラーではなくリンクエラーです。

コレは先述の「定義は単数でなければならない」というルールに反するのです。

もそっと詳しく書くと、関数 void hoge_func() はファイル foo.c とファイル bar.c の二箇所に存在するのです。

なぜ件のソースコードはコンパイルやリンクが通ったか

おそらく件の ***_main.c にある *** はモジュール名が入ると思います。

自分の経験から察するにモジュールはミニゲームかメニュー画面のようなものだと察します。

*** に入るのは minigame_00 とか menu_map_select だと思います。

さらにインクルードされるファイルに記述されている関数に

void minigame_00_input()
{
	/* 入力処理 */
}

のようにモジュール名を関数名に含めるようなコーディングルールが設定されていたのではないかと思います。(これらの関数はモジュール内での参照にとどまるのが前提です)

そうすると結果的に関数名の衝突が起こりません。

件の会社はこの辺の事情を知っていたのか

余計なお世話にも聞こえるかも知れませんが、こういうことが気になるものです。

推測を元に書きましたが、このコーディングスタイルは"モジュール名を関数名に含める"というコーディングルールがあるから成立しています。

ミニゲーム集を作っていたり似たようなメニュー群を作っていたりすると、「予め出来上がったファイルをコピーして新しいファイルの雛形とする」っていう事をよくやります。

例えば minigame_00.c を minigame_01.c という名前でコピーして新しいファイルにします。そのときに minigame_01.c の中の関数もリネームするのですが、うっかりリネームに漏れがあったりするとやはり隣家エラーが起きます。アホみたいな話ですが、1個だけ mini_game_00_*** なんてシンボルになっていたりして置換できなかったなんて事があるんです。

こんな置換漏れがあっても(モジュール内の参照にとどまるシンボルに限りますが)リンクエラーが起きない記述方法があります。

それには下記のように static をつけます。

static void hoge_func()
{
	/* 適当にアレする */
}

こうすると hoge_func はコンパイル単位にスコープが限定されるので名前の衝突が起きません。

こんな感じで static 宣言されているならこの辺の事情を知っていると思ってもいいと思います。

まとめ

というわけで、「.cファイルをインクルードしてはいけない理由」は「.cファイルをインクルードするとグローバル関数やグローバル変数を複数定義してしまう可能性が高くなるから」だと思います。

コンパイルして検証とか何もしてないので間違ってないかとビクビクしています。

小川小川 2009/08/13 00:12 「定義は単数でなければならない」というルールについて、良く分かりました。俺もコンパイルして試してはないですが、foo.cとbar.cで何が起こるかぐらいは分かります。得てしてこういうことが起こってしまうので、.hに宣言を書いてそれをincludeしなさい、ひいては.cをincludeすんのやめなさい、っていうことですね。

んで、***の部分はご推察の通りです。あえて間違いを指摘するなら、モジュール名じゃなくてプロジェクト名です。プロジェクトのメイン部分ですwww
200KB弱にも及ぶ***_main.cと、合計200KBを越える***_sub(系).cとで構成されており、基本的に担当プログラマ(メインさん)以外は、***_sub(系).cをincludeしたりしませんでした。

今見たら、***_sub(系).cの関数にはきちんとstatic宣言されていました。もちろん、中には公開関数もあります。それを末端(俺ら)が使いたいときは、***_main.hをincludeする、という案配でした。別にそういうルールやコーディング規約があった訳ではないので、なんとなく使ってる雰囲気は拭えないカンジなんですけどねー(笑

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/ako_bay/20090808/1249671947