C言語のプログラミングの勉強をしています。
そこで2重定義というものを知り調べたのですが、良く分かりませんでした。コンパイルの仕組みなども併せて教えてください。お願いいたします。
恐れ入りますが、どなたか初心者にも分かる位のレベルで教えて頂けますでしょうか? 簡単な例があると助かります。
不明点
・2重定義とは例えば1つの*.hを2つ以上の*.cでインクルードする場合にのみ有効なのか?
自分で調べた結果
2重定義防止用として
#ifndef HOGE
#define HOGE
~~~~~~~~
#endif
上記のようなことを一般的には行うことは分かったのですが、
これをやったことでどうなるのか??
No.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』に詳しい情報があります。
コンパイル時間とか計測して具体的に分かります。
長々とたくさん書き込んでしまいましたが分かってもらえましたか?効果とか。
・以上。おわり。
参考URL:http://www.02.246.ne.jp/~torutk/cxx/file/include …
No.5
- 回答日時:
>・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に重複インクルードの回避を入れることで、
コンパイルが可能になります。
No.4
- 回答日時:
★簡単なコードですか。
>お時間に余裕があれば、簡単なコード例みたいなものを記述して頂けると助かります。
↑
動作確認用ですかね。
・この質問は次のどっちの意味でしょうか?
(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度以上のインクルード指示をスキップさせて、コンパイル時の速度を上げることが可能
となります。それだけです。
・以上。→私も普段、二重インクルード防止対策としてヘッダを作成しています。随分前から。
No.3
- 回答日時:
宣言と定義の違いについて勉強してください。
宣言 の例:
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重定義しているとします。」というのは日本語的に変です。
二重定義とは、変数名とか関数名とか型名とか、それぞれの名前に対して適用する概念だからです。
この回答への補足
回答有難うございました。
そもそも「ファイルの先頭から最後尾まで#ifndef HOGEのように2重定義しているとします。」というのは日本語的に変です。
はHOGEによって2重定義を防止しているとします。の間違えです。
No.2
- 回答日時:
★前回ちょっと間違ったね。
ごめん。>次の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 内で記述された関数定義は展開されなくなります。
・よって、翻訳単位(ソースファイル単位)で防止は出来ますが、翻訳単位が別々の場合は
二重定義は防止できません。
・以上。
No.1
- 回答日時:
★基本的にヘッダにはソースを記述しません。
・理由は、あるヘッダの中に 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が適用されていない状態なのでしょうか?
分かりづらい質問ですが、宜しくお願いいたします。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- HTML・CSS 全部のアクセスを指定したページに転送させたい 2 2022/06/28 16:33
- 数学 モデルのパラメータの定義がいまいちわかりません。 3 2022/10/11 15:16
- 相続・贈与 遺言書の書き方 2 2022/09/18 12:49
- その他(開発・運用・管理) WindowsからSSHでサーバーにあるファイルをダウンロードできない…。 3 2022/04/24 11:08
- C言語・C++・C# C言語について。 7 2023/01/26 16:42
- 政治 私の憲法9条改正案はどうですか? これだったら改憲反対派も賛成してくれますか? 【9条】(平和主義) 3 2022/12/22 22:16
- 政治 立民案で被害者救済を本当にできるのだろうか? 立民の限界を感じる。 特定財産損害誘導行為による被害の 3 2022/11/05 21:20
- 政治 個人的に、憲法改正案を考えてみました。 意見を聞かせてください。 特に、9条(第2章 - 戦争の放棄 3 2023/02/22 22:08
- 数学 正規数の定義で分からないことがあります。 正規数の定義について専門書において 「xがr進正規であると 1 2023/07/17 20:50
- その他(プログラミング・Web制作) COBOL数値転記をCOPY句内での仕様 6 2022/06/15 18:48
このQ&Aを見た人はこんなQ&Aも見ています
-
プロが教える店舗&オフィスのセキュリティ対策術
中・小規模の店舗やオフィスのセキュリティセキュリティ対策について、プロにどう対策すべきか 何を注意すべきかを教えていただきました!
-
関数の実体定義にヘッダファイルの2重定義防止方法が効かない?
C言語・C++・C#
-
ソース内の行末に\\
C言語・C++・C#
-
C言語のポインタに直接アドレスを割り振りしたい
C言語・C++・C#
-
-
4
分割コンパイルの#defineについて
C言語・C++・C#
-
5
1 つ以上の複数回定義されているシンボルが見つかりました
C言語・C++・C#
-
6
多重定義が起きている?--lnk2005エラー:VC++
C言語・C++・C#
-
7
「#undef」と「#define」の使い方について
C言語・C++・C#
-
8
IG、ACC、+B、ILL
国産バイク
-
9
#defineが使用するメモリ領域について
C言語・C++・C#
-
10
バッファとは何ですか
C言語・C++・C#
-
11
ローカル変数の多重定義
C言語・C++・C#
-
12
C言語での引数の省略方法
C言語・C++・C#
-
13
C言語で、メモリを解放しないで終わるプログラム
C言語・C++・C#
-
14
関数から配列を返すには?
C言語・C++・C#
-
15
C言語 exitの使い方
C言語・C++・C#
-
16
Excelで3E8を3.00E+8にしない方法を教えてください。
Excel(エクセル)
-
17
define で 配列
C言語・C++・C#
-
18
<unistd.h>をVisualStudioでつかえるようにする
C言語・C++・C#
-
19
#defineの定数を文字列として読み込む
C言語・C++・C#
-
20
C言語で構造体のメンバを簡単に出力する方法ありますか?
C言語・C++・C#
関連するカテゴリからQ&Aを探す
おすすめ情報
このQ&Aを見た人がよく見るQ&A
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
DWORDの実際の型は何でしょうか
-
2重定義って??
-
visualstudio C# テキストボッ...
-
関数の実体定義にヘッダファイ...
-
変数の型を定義しなかった場合...
-
C++のfor文について
-
構造体の宣言でエラーが出ます。
-
C++/CLIでネイティブの構造体を...
-
typedef enumの使い方を教えて...
-
C言語のコンパイルエラー
-
ハンドルされていない例外が発...
-
コンパイルすると error C1083 ...
-
void func( void )について
-
クラスのメンバ関数を別ファイ...
-
C++デバックエラーについて詳し...
-
long型の定数の末尾にLを付ける...
-
配列を用いない最大値の求め方
-
直接アドレス指定のポインタの...
-
C++ template operator T()
-
構造体の要素すべてに対する四...
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
DWORDの実際の型は何でしょうか
-
typedef enumの使い方を教えて...
-
C++のfor文について
-
long型の定数の末尾にLを付ける...
-
2重定義って??
-
関数の実体定義にヘッダファイ...
-
main.c:7:43: warning: implici...
-
変数の型を定義しなかった場合...
-
visualstudio C# テキストボッ...
-
ハンドルされていない例外が発...
-
C++でboolにintの値を代入する...
-
enumについて
-
構造体の宣言でエラーが出ます。
-
【#define】 defineで定義した...
-
C++ クラスをメンバにもつクラ...
-
0除算を判定したい
-
構造体の要素すべてに対する四...
-
値を返り値に返すのと参照渡し...
-
namespace定義の使い方
-
GCCで暗黙の型変換の警告を出し...
おすすめ情報