
こんにちは。
現在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++);
}
____________________________________________________________
No.6ベストアンサー
- 回答日時:
>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);
とやっても引き継がれません。
Wr5さん、毎度の御回答ありがとうございます。
>・・・この処理は1つのコンパイル単位(通常はソースファイル1つ(他のソースファイルがインクルードされていればそれが展開された状態))で意味を持つものです。
main.cとsub.cで同じファイルをインクルードしないように…ではありませんのでお間違えなく。
大変重要なアドバイスをありがとうございます。
「main.cとsub.cで同じファイルをインクルードしないように」
という用途でインクルードガードを使う事は、間違いであるという
ご指摘を確認するために、main.cとsub.cでfunc.hをインクルードし、
両方のプログラムで関数を呼び出すようにし、コマンドラインで
gcc main.c sub.c
とタイプしてビルドを試みると、
リンク時にfunc関数の多重定義によるでエラーが出ました。
これにより、仰られた事の意味が理解できました。
No.5
- 回答日時:
●func.h の
void func(char *p)を static void func(char *p) に
すれば、インクルードしたソースファイル内だけで有効な
静的関数となる為に、複数のソースファイルモジュールで、
#include "func.h" を記述しても問題なく動作します。
但し、同一のモジュールを複数のソースファイルに記述する
のと同じ事になるのでその分実行モジュールのサイズが大きく
なります。
又、func()モジュールを変更する時、単独のソースファイル
にしておけば、func()モジュールのソースファイルのみを再
コンパイルしてリンクするだけで良いのに対し、ヘッダファイル
に記述した場合、インクルードしたソースファイル全てを再
コンパイルする必要があります。
新たな情報提供、ありがとうございます。
ヘッダファイルに全てを記述してしまうと、
効率的なモジュール化ができないという事だと解釈しました。
>ヘッダファイルに記述した場合、インクルードしたソースファイル全てを再
コンパイルする必要があります。
これも見落としていました。
こういった問題もあるのですね。
No.4
- 回答日時:
前の回答者の理由以外にも、関数の実装を気にせずにコードが書けるという利点があります。
一人でプログラムを書いているとあまりありがたみが感じられないかもしれませんが、たとえばデータの入出力部とデータ構造部を分けて作業する場合、ヘッダーファイルに書かれた部分については、片方が修正している間もう片方の作業はとまってしまいます。関数の実装というのはその宣言より変化が激しいため、この「ヘッダーファイルを修正する」という作業が頻繁に発生し、結果全体で無駄な時間が増加します。
それに、たとえばprintfの中身がどうなっているかを理解しないでもprintfはその呼び出し方を知っていれば利用できますし、printfの中の処理が変わってもコンパイルするのはprintfのあるオブジェクトファイルだけで済みます(リンクはしなおす必要はありますが)。そのようなことができるのはprintfがヘッダーファイルに宣言しか書いていないためで、その実装が書かれていたらこうは行きません。
以上、冒頭に#include "hoge.c"がずらずら並ぶソースをメンテしたことのあるものからの回答でした。
(まれによくあるから困る)
詳しい御説明、ありがというございます。
僕は大規模なプログラムを作った事がないので、
関数の本体をヘッダファイルではなく別のソースファイルに記述する方が
効率的だという事を、まだ実感できていないのですが、
御話を聞いていると、何となく分かってきました。
No.2
- 回答日時:
>ちなみに、関数やクラスのメンバ関数も一緒に、クラスの内容をまるごとヘッダファイルに記述しても、
>今までの所、全く問題なく動作しています。
まぁ、やってみればわかるかと思いますが…
例に示された場合だと
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++のテンプレートとかでしょうかねぇ。
# 他に詳しいか方から回答付くでしょうけど。
ユニークな御回答ありがとうございます。
なるほど、ソースファイルの個数が増えるほど、
ヘッダファイルに関数本体を記述するリスクが増えるという事ですね。
了解致しました。
No.1
- 回答日時:
この程度では問題無いでしょう。
これが、例えば sub.c というファイルにも分割されていて、コンパイルにより main.c→main.obj と sub.c → sub.obj をリンクして main.exe にする、とします(拡張子はVisual C++の例)
こうした場合、 main.c にも sub.c にも func.h によって、 関数funcの実体が存在することになります。これを一つにリンクしようとすると、同じ名前のものが二つあるのでエラーになります。仮にエラーにならなかったとしても、どちらを呼べばいいか不明だし、同じものが複数あるのは無駄になります。
・関数のプロトタイプ
・クラスのメンバ関数を除いた部分(いわゆる「クラスの骨格」)
・マクロ
といったものは、コンパイル時に各ソースファイルに必要なので、ヘッダファイルに書いて共有するのが便利ですが、実体は上のような状態になるので書きません。
例外はインライン関数(class宣言中に記述するものも含む)やテンプレートなどで、マクロみたいなものなのでヘッダに書くことができます。
御回答ありがとうございます。
なるほど、ヘッダファイルのインクルードによって、同じ関数やクラスの実態が、
1つのプログラムに複数存在する事が、基本的な問題なのですね。
stdio.hなどのヘッダファイルを見てみると、
#ifndef _STDIO_H_
#define _STDIO_H_
・
・
・
#endif
といったディレクティブを利用し、各コードが1つのプログラムに1つしかないように、工夫をしているようでした。
こういったディレクティブを上手く利用すれば、ヘッダファイルに関数やクラスの本体を記述しても大丈夫だと思ったのですが、他に何か問題は起こり得ないのでしょうかね~
特に自作のクラスは、メンバ関数などもヘッダファイルにまとめて記述すると使いやすいので、気になっています。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# 宣言する関数の形が決まっている状態で、 str1とstr2の文字列をこの順に引っ付けてstrに保存し 2 2022/05/30 18:21
- C言語・C++・C# プログラミングの授業の課題です 1 2023/01/17 22:15
- C言語・C++・C# c言語配列の結合についてです。 なぜうまくいかないのでしょうか。 #include <stdio.h 4 2022/05/30 22:42
- C言語・C++・C# c言語 プログラムのエラー 1 2023/02/11 20:31
- C言語・C++・C# メインプログラムに#include <algorithm>を書いて、 そのメインプログラムが // 3 2023/05/02 11:24
- C言語・C++・C# 至急教えてください! プログラミングの問題です! お願いします! 出力2と全く同じ出力をするように、 2 2022/06/22 23:10
- C言語・C++・C# 至急教えてください!プログラミングの問題です。 割られる整数と割る整数を受け取って、商と余りを出力す 3 2022/07/05 10:23
- C言語・C++・C# const char** p;のとき、free(p)でC4090エラーとなるのはなぜですか 3 2023/03/31 16:28
- C言語・C++・C# 至急教えてください。プログラミングの問題です。 最初に正の整数nの入力を受け付け、次に分数の分子と分 1 2022/07/19 17:03
- C言語・C++・C# 至急教えてください。プログラミングの問題です。 malloc関数を使ってください!お願いします! 最 1 2022/07/21 09:28
関連するカテゴリからQ&Aを探す
おすすめ情報
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
” OS ビルド ” の意味が分か...
-
VBAを何回も作り直して、容量が...
-
エクセルVBAではRound...
-
C言語のコンパイルができません
-
外部シンボル "_main"は未解決です
-
【VC++6.0(MFC)】警告「LINK : ...
-
Makefile作成時の拡張子.oとは?
-
セミコロンについて
-
Visual Studio .net ヘッダーフ...
-
cygwinでのエラーについて
-
MO, PO, POT, ファイルの開き方...
-
C言語をコンパイル後の膨大なフ...
-
ビルドとリビルドの違いを教え...
-
Fortranについて教えてください
-
「DeclareステートメントにPtrS...
-
C言語でヘッダファイルにグロー...
-
クリティカルエラー Expressio...
-
マクロ コンパイルがグレーバック
-
sys/types.hの必要性について
-
GCC Developer Liteでヘッダフ...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
” OS ビルド ” の意味が分か...
-
VBAを何回も作り直して、容量が...
-
エクセルVBAではRound...
-
C言語でヘッダファイルにグロー...
-
1 つ以上の複数回定義されてい...
-
「fatal error C1189」を回避す...
-
Makefile作成時の拡張子.oとは?
-
外部シンボル "_main"は未解決です
-
<math.h>があるのにsqrtが・...
-
ILSpyで、デコンパイルできない。
-
クリティカルエラー Expressio...
-
【VC++6.0(MFC)】警告「LINK : ...
-
マクロ コンパイルがグレーバック
-
セミコロンについて
-
複数のサブディレクトリを一緒...
-
C言語のコンパイルができません
-
PRO*C コンパイルエラー
-
ExcelVBAで『ByRef 引数の型が...
-
コンパイルエラー:ユーザ定義...
-
プリコンパイルエラーについて
おすすめ情報