こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

ヘッダファイルに関数本体を書き込めないのか?

こんにちは。

現在WindowsVistaでCおよびC++を使ってプログラミングを行っています。

最近になって思ったのですが、普通、ヘッダファイルに記述する内容は、
・関数のプロトタイプ
・クラスのメンバ関数を除いた部分(いわゆる「クラスの骨格」)
・マクロ
といったものだと言われています。
そして、関数の実態やクラスのメンバ関数などは、
別のソースファイルに記述するように言われています。

なぜ、ヘッダファイルに関数の実態や、クラスのメンバ関数を記述するべきではないでしょうか?
あるいは、プログラムの内容に応じて、関数やクラスの内容を、
ヘッダファイルにまるごと記述してもよい場合と悪い場合があるのでしょうか?

こういった事について、何か御存じの方がいらっしゃれば、是非アドバイスをお願い致します。
(難しい問題なので、なるべく詳しい説明を頂けると、大変助かります。)

ちなみに、関数やクラスのメンバ関数も一緒に、クラスの内容をまるごとヘッダファイルに記述しても、
今までの所、全く問題なく動作しています。
例えば、以下のようなプログラムは、何の問題もなく動作します。

●main.c
____________________________________________________________
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "func.h"

void main(void)
{
char str[80];
puts("文字列を入力せよ");
gets(str);
func(str);
}
____________________________________________________________


●func.h
____________________________________________________________
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

void func(char *p)
{
puts("入力された内容は以下の通り。");
while(*p)
putchar(*p++);
}
____________________________________________________________

投稿日時 - 2011-08-17 01:05:25

QNo.6947647

困ってます

質問者が選んだベストアンサー

>stdio.hなどのヘッダファイルを見てみると、
>#ifndef _STDIO_H_
>#define _STDIO_H_
>    :
>#endif
>といったディレクティブを利用し、各コードが1つのプログラムに1つしかないように、工夫をしているようでした。

それはインクルードガードです。
今回の問題とは別です。

とはいえ、掲示されたヤツにちょうどパターンがありますが…。

●main.c
____________________________________________________________
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#include "func.h"

●func.h
____________________________________________________________
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

で、main.cをコンパイルするときに、
stdio.h/stdlib.h/ctype.h/string.hが2重にインクルードされます。
define定義などは同じ名前で複数定義するとエラーになりますので、
そうならないようにするために条件付きコンパイルで2回目以降を無効にするようにガードしている。
ということです。

なお、この処理は1つのコンパイル単位(通常はソースファイル1つ(インクルードされていればそれが展開された状態))で意味を持つものです。
main.cとsub.cで同じファイルをインクルードしないように…ではありませんのでお間違えなく。
# 別のファイルのコンパイル処理に移る場合、以前のファイルでdefineされた内容は一端クリアされます。
# その上で、コンパイルスイッチやMakefile、ビルド環境で指定されているシンボルが定義し直されます。
# あと、環境依存のものとか。

なので…main.cの次にsub.cがコンパイルされるから…
main.cで
#define TESTMESSAGE ("定義しました")
sub.cで
printf("%s\n", TESTMESSAGE);
とやっても引き継がれません。

投稿日時 - 2011-08-17 23:59:23

お礼

Wr5さん、毎度の御回答ありがとうございます。

>・・・この処理は1つのコンパイル単位(通常はソースファイル1つ(他のソースファイルがインクルードされていればそれが展開された状態))で意味を持つものです。
main.cとsub.cで同じファイルをインクルードしないように…ではありませんのでお間違えなく。

大変重要なアドバイスをありがとうございます。
「main.cとsub.cで同じファイルをインクルードしないように」
という用途でインクルードガードを使う事は、間違いであるという
ご指摘を確認するために、main.cとsub.cでfunc.hをインクルードし、
両方のプログラムで関数を呼び出すようにし、コマンドラインで
gcc main.c sub.c
とタイプしてビルドを試みると、
リンク時にfunc関数の多重定義によるでエラーが出ました。
これにより、仰られた事の意味が理解できました。

投稿日時 - 2011-08-18 03:43:50

ANo.6

このQ&Aは役に立ちましたか?

0人が「このQ&Aが役に立った」と投票しています

回答(6)

ANo.5

●func.h の
void func(char *p)を static void func(char *p) に
すれば、インクルードしたソースファイル内だけで有効な
静的関数となる為に、複数のソースファイルモジュールで、
#include "func.h" を記述しても問題なく動作します。

但し、同一のモジュールを複数のソースファイルに記述する
のと同じ事になるのでその分実行モジュールのサイズが大きく
なります。
又、func()モジュールを変更する時、単独のソースファイル
にしておけば、func()モジュールのソースファイルのみを再
コンパイルしてリンクするだけで良いのに対し、ヘッダファイル
に記述した場合、インクルードしたソースファイル全てを再
コンパイルする必要があります。

投稿日時 - 2011-08-17 03:47:56

お礼

新たな情報提供、ありがとうございます。
ヘッダファイルに全てを記述してしまうと、
効率的なモジュール化ができないという事だと解釈しました。

>ヘッダファイルに記述した場合、インクルードしたソースファイル全てを再
コンパイルする必要があります。

これも見落としていました。
こういった問題もあるのですね。

投稿日時 - 2011-08-17 21:33:13

ANo.4

前の回答者の理由以外にも、関数の実装を気にせずにコードが書けるという利点があります。

一人でプログラムを書いているとあまりありがたみが感じられないかもしれませんが、たとえばデータの入出力部とデータ構造部を分けて作業する場合、ヘッダーファイルに書かれた部分については、片方が修正している間もう片方の作業はとまってしまいます。関数の実装というのはその宣言より変化が激しいため、この「ヘッダーファイルを修正する」という作業が頻繁に発生し、結果全体で無駄な時間が増加します。

それに、たとえばprintfの中身がどうなっているかを理解しないでもprintfはその呼び出し方を知っていれば利用できますし、printfの中の処理が変わってもコンパイルするのはprintfのあるオブジェクトファイルだけで済みます(リンクはしなおす必要はありますが)。そのようなことができるのはprintfがヘッダーファイルに宣言しか書いていないためで、その実装が書かれていたらこうは行きません。

以上、冒頭に#include "hoge.c"がずらずら並ぶソースをメンテしたことのあるものからの回答でした。
(まれによくあるから困る)

投稿日時 - 2011-08-17 03:35:36

お礼

詳しい御説明、ありがというございます。
僕は大規模なプログラムを作った事がないので、
関数の本体をヘッダファイルではなく別のソースファイルに記述する方が
効率的だという事を、まだ実感できていないのですが、
御話を聞いていると、何となく分かってきました。

投稿日時 - 2011-08-17 21:27:53

ANo.3

Wr5

ありゃ…なにげに長文(?)で書いていたら…見事にカブってしまいました…。
# main.cとsub.c、main.objとsub.objとか。

投稿日時 - 2011-08-17 01:51:25

お礼

全然気にしないでください。
Cプログラマなら、mainやsubなどのキーワードは、説明でよく使うはずなので。

投稿日時 - 2011-08-17 21:17:13

ANo.2

Wr5

>ちなみに、関数やクラスのメンバ関数も一緒に、クラスの内容をまるごとヘッダファイルに記述しても、
>今までの所、全く問題なく動作しています。

まぁ、やってみればわかるかと思いますが…
例に示された場合だと
main.c以外にSub.cというソースファイルでもfunc.hをインクルードしたとします。
もちろんSub.cに記述した関数をmain()からコールするようにしますし、Sub.cでもfunc()をコールするようにします。

さて、リンカー君はmain.objにあるfunc()とSub.objにあるfunc()とどっちを使ったらいいですかね?
「今日はハッピーな気分だからmain.objのfunc()にリンクしよう。
 いやいや、バッドな気分だからSub.objのfunc()だよ。」
と……。
実行中にエラーが出たら…リンカー君の気分を推定する楽しい作業から始める必要がありますね。

…当然ですが、実際にはリンク時にエラーになります。


リンク時にエラーが出なかったとしましょう。
main()でコールしたfunc()に不具合があったので修正してビルドしたら…
ナニも変更していないハズのSub.cも何故かコンパイルされました。
さて…これで2000ファイルもあったら……
コンパイルするだけでタバコ休憩できますね。
func.hで修正した内容にミスがありました。
2000以上のエラーが出てきて…わぁい大変ダァ。


ヘッダにコードをかけるとしたら…C++のテンプレートとかでしょうかねぇ。
# 他に詳しいか方から回答付くでしょうけど。

投稿日時 - 2011-08-17 01:49:36

お礼

ユニークな御回答ありがとうございます。
なるほど、ソースファイルの個数が増えるほど、
ヘッダファイルに関数本体を記述するリスクが増えるという事ですね。
了解致しました。

投稿日時 - 2011-08-17 21:15:00

ANo.1

この程度では問題無いでしょう。

これが、例えば sub.c というファイルにも分割されていて、コンパイルにより main.c→main.obj と sub.c → sub.obj をリンクして main.exe にする、とします(拡張子はVisual C++の例)
こうした場合、 main.c にも sub.c にも func.h によって、 関数funcの実体が存在することになります。これを一つにリンクしようとすると、同じ名前のものが二つあるのでエラーになります。仮にエラーにならなかったとしても、どちらを呼べばいいか不明だし、同じものが複数あるのは無駄になります。

・関数のプロトタイプ
・クラスのメンバ関数を除いた部分(いわゆる「クラスの骨格」)
・マクロ
といったものは、コンパイル時に各ソースファイルに必要なので、ヘッダファイルに書いて共有するのが便利ですが、実体は上のような状態になるので書きません。

例外はインライン関数(class宣言中に記述するものも含む)やテンプレートなどで、マクロみたいなものなのでヘッダに書くことができます。

投稿日時 - 2011-08-17 01:39:02

お礼

御回答ありがとうございます。
なるほど、ヘッダファイルのインクルードによって、同じ関数やクラスの実態が、
1つのプログラムに複数存在する事が、基本的な問題なのですね。
stdio.hなどのヘッダファイルを見てみると、
#ifndef _STDIO_H_
#define _STDIO_H_
    ・
    ・
    ・
#endif
といったディレクティブを利用し、各コードが1つのプログラムに1つしかないように、工夫をしているようでした。
こういったディレクティブを上手く利用すれば、ヘッダファイルに関数やクラスの本体を記述しても大丈夫だと思ったのですが、他に何か問題は起こり得ないのでしょうかね~
特に自作のクラスは、メンバ関数などもヘッダファイルにまとめて記述すると使いやすいので、気になっています。

投稿日時 - 2011-08-17 21:05:33

あなたにオススメの質問