- ベストアンサー
2重定義って??
C言語のプログラミングの勉強をしています。 そこで2重定義というものを知り調べたのですが、良く分かりませんでした。コンパイルの仕組みなども併せて教えてください。お願いいたします。 恐れ入りますが、どなたか初心者にも分かる位のレベルで教えて頂けますでしょうか? 簡単な例があると助かります。 不明点 ・2重定義とは例えば1つの*.hを2つ以上の*.cでインクルードする場合にのみ有効なのか? 自分で調べた結果 2重定義防止用として #ifndef HOGE #define HOGE ~~~~~~~~ #endif 上記のようなことを一般的には行うことは分かったのですが、 これをやったことでどうなるのか??
- みんなの回答 (6)
- 専門家の回答
質問者が選んだベストアンサー
★ちょっと見落としていた事があったので追記。 ・ヘッダ内に構造体、共用体、ビットフィールドを typedef でユーザ型に再定義されている記述が 存在すると二重にインクルードしたとき『型の再定義』エラーとなります。 例えば // 構造体型 typedef struct user_t { : 略 : } USER, *LPUSER; ↑ このヘッダを2度以上インクルードすると二度目以降は『型の再定義』エラーとなります。 このエラーを防ぐ(回避する)ためにプロプロセッサ命令を使って条件コンパイルすれば良い。 その回避方法として (1)#ifndef 定数名 #define 定数名 (この場所に typedef のユーザ型を定義) #endif (2)#if !defined(記号定数) #define 定数名 (この場所に typedef のユーザ型を定義) #endif とします。 ・質問の原点に戻ると >自分で調べた結果 >2重定義防止用として >#ifndef HOGE >#define HOGE >~~~~~~~~ >#endif ↑ これは typedef などの再定義エラーを起こさせないための『二重インクルード防止対策』 という事になります。 ・よって簡単なサンプルを載せると次のようになります。 サンプル: [hoge.h] -------------------- #if !defined(_HOGE_H_) #define _HOGE_H_ // バイト型 typedef unsigned char BYTE; // ワード型 typedef unsigned short WORD; // ユーザ型(構造体) typedef struct user_t { : 略 : } USER, *LPUSER; // 記号定数の定義 : 略 : // マクロ関数の定義 : 略 : // 関数のプロトタイプ宣言 : 略 : #endif // 2重 include 防止対策 /* End of hoge.h */ -------------------- [main.c] -------------------- #include "hoge.h" #include "hoge.h" ←わざと2回インクルード // メイン関数 int main( void ) { : (省略) : return 0; } /* End of main.c */ -------------------- 解説: ・『hoge.h』に typedef の記述がなく単純な『記号定数』、『マクロ関数』と 『関数のプロトタイプ宣言』だけの場合は『わざと2回インクルード』しても エラーは起こりません。でもサンプルの『hoge.h』では typedef の記述として ユーザ型の USER、LPUSER が定義されています。 ・このとき『わざと2回インクルード』した場合には typedef の再定義エラーと なってしまいコンパイルできません。これは構造体、共用体、ビットフィールドでは typedef で同じ内容を複数回定義する事が出来ないからです。でもそれ以外なら typedef で同じ内容を複数回定義してもエラーにはなりません。 例えば typedef unsigned char BYTE; typedef unsigned char BYTE; とか typedef void (*PFUNC)(int a); typedef void (*PFUNC)(int a); とかは同じ内容を typedef しても何故かエラーとはなりません。 最後に: ・『#ifndef 定数』とか『#if !defined(定数名)』は二重インクルードの防止対策以外に 複数回インクルードした場合にコンパイル速度を速める効果もあります。 この辺は下の『参考URL』に詳しい情報があります。 コンパイル時間とか計測して具体的に分かります。 長々とたくさん書き込んでしまいましたが分かってもらえましたか?効果とか。 ・以上。おわり。
その他の回答 (5)
- aris-wiz
- ベストアンサー率38% (96/252)
>・2重定義とは例えば1つの*.hを2つ以上の*.cでインクルードする場合にのみ有効なのか? このような場合は「2重定義」ではなく「重複インクルード」 「二重インクルード」と言うほうがわかりやすいでしょう。 #ifndef HOGE #define HOGE ~~~~~~~~ #endif 上記はプリプロセッサと呼ばれ、コンパイル時に置換されます。 #defineで定義される識別子も一度定義されると#undefを使って、 定義を解除されるまでそれまでの定義が有効となり同名の識別子を 定義することはできません。 >これをやったことでどうなるのか?? #ifndef HOGE まず、識別子「HOGE」が定義されているかどうかを調べます。 「HOGE」が既に定義されていれば、IF文が成り立たなくなります。 #define HOGE 「HOGE」が定義されていなかった場合「HOGE」を定義します。 これにより、2回目以降には既に定義されているため、 「#ifndef HOGE」以降の記述を読み飛ばします。 #endif 「#ifndef」に対するブロックの終了を意味します。 この「#ifndef - #endif」のブロック内にある記述が 2回目以降全て読み飛ばされることになります。 なぜ必要か。 まず、C言語では同じ手続きに対する定義は、 一意のものである必要があるとされています。 構造体の型などを新たに定義する場合、 それを定義したヘッダが別のヘッダ内でインクルードされ、 そのヘッダが複数のソースファイルに参照された場合に、 「2重定義」というエラーが起こります。 この構造体を使うソースファイルごとにインクルードされれば、 問題ないのですが、公開されたヘッダがどのように扱われるか わからないので、それを回避するために既にインクルードされた ヘッダをプリプロセッサを使って読み飛ばしを行います。 2重定義エラー例 *********************** hoge1.h typedef struct _test{ int aaa; }TEST; *********************** *********************** hoge2.h #include "hoge1.h" int sub1( TEST *ptest ); int sub2( TEST *ptest ); *********************** *********************** sub1.c #include "hoge2.h" int sub1( TEST *ptest ) { return 0; } *********************** *********************** sub2.c #include "hoge2.h" int sub2( TEST *ptest ) { return 0; } *********************** *********************** main.c #include "hoge2.h" int main( int argc, char* argv[] ) { TEST test; sub(&test); sub2(&test); return 0; } *********************** hoge1.hに重複インクルードの回避を入れることで、 コンパイルが可能になります。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★簡単なコードですか。 >お時間に余裕があれば、簡単なコード例みたいなものを記述して頂けると助かります。 ↑ 動作確認用ですかね。 ・この質問は次のどっちの意味でしょうか? (1)ヘッダ内に関数の定義があり同じソース内に複数回のインクルードをしたときに 関数の実体を1回だけにするために #ifndef を使っている意味。 (2)それともヘッダ全体の定義を1回だけにする二重インクルードの防止対策。 やり取りしながらひょっとして (2) かな?と思ってきた。 もし(2)の場合なら普段ヘッダを作成する時に必ず二重インクルードの防止対策として #ifndef、#if !defined() を記述しています。 ・この場合はヘッダの先頭に『#ifndef 定数名』や『#if !defined(定数名)』を記述、 ヘッダの最後には『#endif』を記述することで全体を二重インクルード防止対策となる。 >C言語のプログラミングの勉強をしています。 >そこで2重定義というものを知り調べたのですが、良く分かりませんでした。 >コンパイルの仕組みなども併せて教えてください。お願いいたします。 ↑ 多分、この質問はこちら(二重インクルード防止)のことを知りたいのでしょうね。 と考え説明します。まずは下のサンプルをどうぞ。 [hoge.h] -------------------- #if !defined(_HOGE_H_) #define _HOGE_H_ // コンパイル時に必要なインクルード #include <stdio.h> #include <stdlib.h> #include <string.h> /* ここに記号定数、マクロ関数、関数のプロトタイプ宣言などを記述 */ #endif // 2重 include 防止対策 /* End of hoge.h */ -------------------- [hoge1.c] -------------------- #include "hoge.h" /* ファイル入出力の関数定義など */ /* End of hoge1.c */ -------------------- [hoge2.c] -------------------- #include "hoge.h" /* 文字列操作の関数定義など */ /* End of hoge2.c */ -------------------- [main.c] -------------------- #include "hoge.h" #include "hoge1.c" #include "hoge2.c" // メイン関数 int main( void ) { : (省略) : return 0; } /* End of main.c */ -------------------- 解説: ・上記のサンプルでは hoge.h、hoge1.c、hoge2.c、main.c の4つのファイルがあります。 このファイルは説明の都合上 (1)hoge.h ⇒自作関数ライブラリの共通ヘッダとする (2)hoge1.c⇒自作関数ライブラリのファイル入出力のソースとする (3)hoge2.c⇒自作関数ライブラリの文字列操作のソースとする (4)main.c⇒自作関数ライブラリのテスト用のソースとする という意味としておきます。 ・このとき hoge.h は hoge1.c、hoge2.c の両方でインクルードする共通ヘッダです。 そして自作関数ライブラリのテスト用として main.c を用意。 この main.c はテスト用なので hoge1.c、hoge2.c の両方をインクルードしています。 このように main.c 内で hoge.h が複数回インクルードされる場合は二重インクルードの 防止対策をしておくことで無駄に2度以上 hoge.h をインクルードしないためコンパイルの 処理が早くなります。 ・普通、hoge.h のようなヘッダには関数定義は記述しないで記号定数、マクロ関数、構造体、 関数のプロトタイプ宣言などを記述します。これだけなら複数回インクルードしても特に エラーとはならずに問題は起きません。つまり、二重インクルードの防止対策 #ifndef を 記述しなくても構わないのです。でも複数回インクルードする場合のことも考えて防止対策を しておけばコンパイル時の速度が速くなります。 ・よってこの質問にある >上記のようなことを一般的には行うことは分かったのですが、 >これをやったことでどうなるのか?? ↑ これは1つのソース(翻訳単位)内で同じヘッダを複数回インクルードされる可能性があるときに 2度以上のインクルード指示をスキップさせて、コンパイル時の速度を上げることが可能 となります。それだけです。 ・以上。→私も普段、二重インクルード防止対策としてヘッダを作成しています。随分前から。
- a-saitoh
- ベストアンサー率30% (524/1722)
宣言と定義の違いについて勉強してください。 宣言 の例: extern int i; extern int b[]; int function(int arg1; int argb・・・) ; 定義: int i; int b[100]; typedef struct {int a,b;・・・・} b; int function(int arg1; int argb・・・) { ・・・・・} 宣言とは、コンパイラに対する情報提供です(「○○という物が分割コンパイルされた他のファイルにあるよ」、「○○という関数の引数は□個でそれぞれの型は△△△だよ」)。 定義は何かしらオブジェクトファイルに反映されるものです。 Cに限らず、多くの言語では(僕が知る限り全部ですが)、同じ宣言を多重に行っても害はありませんが、同じ名前に対する定義は1回しか行ってはいけません。 ただ、マクロ定義だけは、二重に行ってもエラーはでなかったように思いますが。コンパイラによってちがうかも。 で、そもそも「ファイルの先頭から最後尾まで#ifndef HOGEのように2重定義しているとします。」というのは日本語的に変です。 二重定義とは、変数名とか関数名とか型名とか、それぞれの名前に対して適用する概念だからです。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★前回ちょっと間違ったね。ごめん。 >次のhoge2.cはifndefによって見ることが出来ないのでしょうか? ↑ 出来ない。→ソース単位で HOGE 定数が定義されているかどうかを #ifndef で確認 するためソースファイルが切り替わると最初は HOGE 定数が定義されていないので インクルードすると hoge.h の内容が展開されます。 >次にhoge2.cがhoge.hをインクルードする時は上記のHOGEが有効なので展開できない? >それともhoge2.cの時もhoge.cと同じ状態(まだ、HOGEが適用されていない状態なのでしょうか? ↑ hoge2.c の時も hoge.c と同じ状態で最初は HOGE 定数が未定義なのでヘッダ内容が 展開されますね。前回の回答間違っていましたね。 ・1つのプログラムが hoge1.c、hoge2.c から構成されている場合、hoge1.c、hoge2.c で hoge.h をそれぞれインクルードするとソース単位で hoge.h の内容が展開されます。 もし hoge.h に関数定義の記述があると hoge1.obj、hoge2.obj 内の両方で同名の関数が存在 するためリンク時にエラーになりますね。このため通常ではヘッダ内に関数定義を記述などは しません。 ・前回はちょっと勘違いしました。 どんな風に勘違いしたかというと hoge.h に関数定義と二重定義防止があるとした場合 hoge1.c で hoge.h をインクルードして hoge2.c でも hoge.h をインクルードして main.c で hoge1.c、hoge2.c の2つをインクルードしていた場合には二重定義防止機能が 働き hoge.h 内で記述された関数定義は hoge1.c の最初の1回だけ展開されて hoge2.c の 場合は hoge.h 内で記述された関数定義は展開されなくなります。 ・よって、翻訳単位(ソースファイル単位)で防止は出来ますが、翻訳単位が別々の場合は 二重定義は防止できません。 ・以上。
お礼
難しいですね。。。お時間に余裕があれば、簡単なコード例みたいなものを記述して頂けると助かります。 無理言ってすみません。。。
- Oh-Orange
- ベストアンサー率63% (854/1345)
★基本的にヘッダにはソースを記述しません。 ・理由は、あるヘッダの中に mylib.c というソースをインクルードしていた場合 そのヘッダを複数の hoge.c、foo.c でインクルードすると mylib.c の関数定義が 同名関数で複数定義されることになります。当然、二重定義となりエラーです。 ・これを防ぐためには最初に hoge.c でヘッダをインクルードしたときの1回だけ 関数定義を有効として foo.c などでヘッダをインクルードしても二度以上では 関数定義を無効にします。この動作を実現するために条件コンパイルの #ifndef を使い >自分で調べた結果 >2重定義防止用として >#ifndef HOGE >#define HOGE >~~~~~~~~ >#endif ↑ このようにします。 ・これで無理やりヘッダの中に記述された関数定義を最初のインクルードのみ関数の 定義と見なします。その後のインクルードでは既に HOGE 定数が定義されているため 関数の定義とはなりません。よって二重定義のエラーはでません。 >これをやったことでどうなるのか?? ↑ 二重定義のエラーを防げる。 ※なおインライン関数の場合は二重定義の防止をするとインライン関数の部分が 展開されなくなるため #ifndef HOGE などは記述してはいけません。 関連: ・同じような方法でヘッダファイルの二重インクルードも防止できます。 同じヘッダファイルを何回インクルードしても最初の1回目だけ機能を有効にすることで コンパイル処理を早くすることが出来ます。ただし、同じヘッダを何度もインクルードする ことは普通はありません。でも良くインクルードするヘッダを1つの my.h としていて foo.c で my.h をインクルード、さらに my.h 内で既にインクルードしている stdio.h を foo.c でまたインクルード指定した場合に二重インクルードを防止できるということです。 ・二重インクルードの方法は #if !defined( _MY_HEAD_ ) #define _MY_HEAD_ #include <stdio.h> #include <stdlib.h> #include <string.h> /* 記号定数やマクロ関数の定義など */ /* 関数のプロトタイプ宣言など */ #endif /* 2重 include 防止対策 */ ↑ このようにします。 >#ifndef HOGE ↑ これと >#if !defined( _MY_HEAD_ ) ↑ これは同じ機能です。 #ifndef が古くからある指定で #if+defined(定数) の組み合わせが新しい指定かな。 ・以上。
お礼
迅速な回答ありがとうございました。 ここで初歩的な疑問がありますので回答お願い致します。 コンパイルの仕組みですが、 1つのhoge.hがありそのなかでは上記の用にファイルの先頭から最後尾まで#ifndef HOGEのように2重定義しているとします。そのhoge.hをhoge1.cとhoge2.cがインクルードすると例えば初めにhoge1.cがhoge.hを見に行くと(展開?)次のhoge2.cはifndefによって見ることが出来ないのでしょうか? hoge1.cがhoge.hをインクルードよってここからHOGEが適用される。 次にhoge2.cがhoge.hをインクルードする時は上記のHOGEが有効なので展開できない?それともhoge2.cの時もhoge.cと同じ状態(まだ、HOGEが適用されていない状態なのでしょうか? 分かりづらい質問ですが、宜しくお願いいたします。
補足
回答有難うございました。 そもそも「ファイルの先頭から最後尾まで#ifndef HOGEのように2重定義しているとします。」というのは日本語的に変です。 はHOGEによって2重定義を防止しているとします。の間違えです。