ファイルの分割で #ifdef,#define,#endifの意味が分かりません。初心者に理解できるように教えてください。例文などあれば助かります。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
ベストアンサー
ファイルの分割と、条件コンパイルは別の話題です。
それぞれがわからないのか、どちらも単独では理解していても組み合わせたときがわからないのか、どうなんでしょう。
ファイルの分割...といってしまうといろいろありますが、
#include
の働きについて言えば、コンパイルの前段階の処理(プリプロセス)として、指定されたファイルを(""と<>でファイルを探す範囲が決められていますが)その場所に展開する、という働きをします。
C
1///aaa.h 2 printf(
C
1///main.h 2#include <stdio.h> 3int main(void){ 4#include "aaa.h" 5 "Hello, World!\n"); 6 retrun 0; 7}
は、コンパイルの前段階でaaa.hが展開されて
c
1#include <stdio.h> //もちろんこちらも展開されますが、長くなるので略 2int main(void){ 3 printf( ///aaa.hが展開された 4 "Hello, World\n"); 5 return 0; 6}
となって処理が進行します。
このような作用を持つため、分割コンパイルに於いては、共通で使用する関数や変数、型の定義をヘッダファイルに書いておくと、それぞれのソースファイルで展開されるのでソースファイルが「完成」する、ということになるものです。
条件コンパイルもプリプロセス命令です。
#define
は、コンパイル前に第一パラメータの文字列を第二パラメータの文字列に置き換えます。この機能を「マクロ」と呼びます(関数型マクロの話はとりあえず棚上げ)
#ifdef
あるいは#if defined
は、パラメータとされた文字列(シンボル)がマクロとして定義されていなかった場合、#else
または#endif
に出会うまでの内容を削除してコンパイルに進みます。定義されていた場合に#else
があれば、#else
の後#endif
までを削除してコンパイルに進みます。#ifndef
あるいは#if not defined
は、シンボルがマクロ定義されていた場合、#else
または#endif
に出会うまでの内容を削除してコンパイルに進みます。
実際には、ソース中の文字列の置き換えの意図がなくても、#ifdef
(あるいは同類の#if
)で使うためだけにマクロを定義するのもよくあることです。
C
1#define AAA 2///AAAを空文字列に置き換える 3//#defineの行をコメントにあるいは削除して動作を比べてみる 4 5#include <stdio.h> 6int main(void){ 7 printf( 8 #ifdef AAA 9 "AAA defined\n" 10 #else 11 "I don't know 'AAA'\n" 12 #endif 13 ); 14 return 0; 15}
とすると、#define AAA
の行があればこれは
C
1int main(void){ 2 printf( 3 "AAA defined\n" 4 ); 5 return 0; 6}
としてコンパイルされ、#define AAA
がなければ
C
1int main(void){ 2 printf( 3 "I don't know 'AAA'\n" 4 ); 5 return 0; 6}
してコンパイルされます。
これらを組み合わせて、「インクルードガード」というテクニックがしばしば(常識といっていいレベルで)使われます。
C
1typedef int I; 2typedef int I;
はエラーになりますが、例えば、
C
1///a.h 2typedef int I;
C
1///b.h 2#include "a.h" 3extern I num;
C
1///main.c 2#include "a.h" 3#include "b.h" 4//以下略
などとしてしまうと、これはこれまでの説明でおわかりかと思いますが
C
1///main.c 2//a.hの分 3typedef int I; 4//ここからb.h分 5typedef int I; 6extern I num;
となってしまい、エラーが発生します。
ここで、
C
1///a.h 2#ifndef A_H 3#define A_H 4typedef int I; 5#endif
としておけば、#include
が展開されると
C
1///main.c 2//a.hの分 3#ifndef A_H //ここではA_Hは定義されていない 4#define A_H //ここでA_Hが定義される 5typedef int I; 6#endif 7//ここからb.h分 8#ifndef B_H 9#define B_H 10#ifndef A_H //この時点ではA_Hは定義済なので... 11#define A_H //削除される 12typedef int I; //削除される 13#endif 14extern I num; 15#endif
結果として
C
1///main.c 2typedef int I; 3extern I num;
となり、意図しない二重定義を防げる、ということになります。
まぁ、最近では多分#pragma onceが使える環境が多くなっていると思うので、そちらを使っておけばいいのかも知れません。不用意に_A_HとかやっちゃうとこれはC++では予約済シンボル(アンダースコア+大文字で始まるシンボル)だったりするので、何が起こっても文句が言えないです。
投稿2020/08/20 23:07
総合スコア7703
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/08/24 15:51
2020/08/24 22:35
0
DMRさん
恐らくインクルードガードの質問かと推測して回答します。
インクルードガードは同じヘッダが複数回呼ばれた場合
ifndefからendifまでを無視することで
多重定義を防ぐためのイディオムです。
最初に読み込まれた時defineで定数を定義しておけば
二度目からはifndefに引っかかり定義部分が無視されるという仕組みです。
wikibooks
ちなみに貴方がstd::coutで標準出力を行いたい時など
iostreamヘッダーをインクルードするかと思いますが。
c++
1#include <iostream> 2#include <iostream>
と二回立て続けに書いても多重定義は発生しません。
それはiostream内にもインクルードガードが
組み込まれているからだと聞いたことがあります(実際確認してませんごめんなさい)
あと使うコンパイラによるかと思われますが
インクルードガードを書かなくとも
c++
1#pragma once
の一行でそのファイルを1度しか呼び出せなくすることが出来るので
そちらのほうが簡潔かと思われます。
下記に書き方の例を記します。
インクルードガードの例
c++
1// func.hpp 2 3// define定数が定義されていないなら#endifまで実行 4// (最初の一回は必ず下記の処理が読み込まれるが二度目からは 5// FUNC_HPPが定義されているので下記#endifまでは無視される) 6#ifndef FUNC_HPP 7#define FUNC_HPP// define定数定義 8// この中に宣言や定義を書く 9void func(); 10 11#endif
c++
1// func.cpp 2#include "./func.hpp" 3#include <iostream> 4 5// 実装を記載 6void func(){ 7 std::cout << "funcが呼ばれた" << std::endl; 8}
c++
1// main.cpp 2#include "./func.hpp" 3 4int main(){ 5 func();// 関数func呼び出し 6 return 0; 7}
pragma onceの例
c++
1// func.hpp 2 3#pragma once// この一文のみでこのヘッダは一度しか読み込まれない 4 5void func(); 6
c++
1// func.cpp 2#include "./func.hpp" 3#include <iostream> 4 5// 実装を記載 6void func(){ 7 std::cout << "funcが呼ばれた" << std::endl; 8}
c++
1// main.cpp 2#include "./func.hpp" 3 4int main(){ 5 func();// 関数func呼び出し 6 return 0; 7}
投稿2020/08/20 16:39
編集2020/08/20 16:47退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。