読者です 読者をやめる 読者になる 読者になる

自習室

こもります

#ifdef と #ifndef

読書 .C++

http://blog.s21g.com/articles/1058

にあるとおり。簡潔。

静的ライブラリを作る練習(ちょー基本的)

C++クックブック

C++クックブック

O'reillyの "C++クックブック"で一番始めにやる練習が、静的ライブラリ、動的ライブラリ、実行ファイルのコンパイル、リンク(アーカイブ)をシェルからやろう、というものなのだが、その中ではまったケースをメモっておく。

"John, Paul, "

とだけ画面に出力する関数を定義した静的ライブラリを作る。中の処理はちょー簡単なんだけど、ファイルの組み合わせで静的ライブラリを作ってみようぜ!っていう練習なのでわざわざ細かくファイルが分割されてる。

構成
  • john.cpp内で "John, "までを出力する関数を書き
  • paul.cpp内で "Paul, "までを出力する関数を書く。
  • johnpaul.cppで、上記二つの関数をつかって連続して"John, Paul, "と出力するようにする。
  • 静的ライブラリlibjohnpaul.a としてまとめる。

john.hppとjohn.cppのソースはこんな感じ

/*	john.hpp	*/
#ifndef JOHN_HPP_INCLUDED
#define JOHN_HPP_INCLUDED

void john();	//prints "John, "

#endif


/*	john.cpp	*/
#include <iostream>
#include "john.hpp"

void john()
{
	std::cout << "John, ";
}


これと同内容のpaul.cpp/hppがあり、johnとpaulをまとめるのがjohnpaul.cpp

/*	johnpaul.hpp	*/
#ifndef JOHNPAUL_HPP_INCLUDED
#define JOHNPAUL_HPP_INCLUDED

void johnpaul();	//Prints "John, Paul, "

#endif


/*	johnpaul.cpp	*/
#include "john.hpp"
#include "paul.hpp"
#include "johnpaul.hpp"

void johnpaul()
{
	john();
	paul();
}
手順

これをコンパイル→アーカイブしていくのだが、その手順は以下。

  1. 各ファイルのコンパイル
    1. g++ -c -o john john.cpp(john.hppをインクルード)
    2. g++ -c -o paul paul.cpp(paul.hppをインクルード)
    3. g++ johnpaul johnpaul.cpp(john.hpp, paul.hpp, johnpaul.hppをインクルード)
  2. 各オブジェクトファイルを静的ライブラリにアーカイブ
    1. ar ru libjohnpaul.a john.o paul.o johnpaul.o
    2. ranlib libjohnpaul.a

てな具合。

誤って #ifdef を使うと

ここで僕は、すべてのヘッダファイル(.hpp)の、#ifndefのとこを、あやまって#ifdefと書いてしまった。

こうすると、#ifdef JOHN_HPP_INCLUDED の判定は常に0 なので、中の void john(); という関数の宣言は行われないことになる。そしてこのままでも john.cpp(およびpaul.cpp) のコンパイルは通る。main()の無い記述なので、宣言しなくても関数の定義だけは出来る。

しかし、johnpaul.cppはコンパイルエラーになる。#includeしたjohn.hpp と paul.hpp の宣言の部分が、#ifdef **_INCLUDED の判定により飛ばされてしまうため、

/*	johnpaul.cpp	*/
#include "john.hpp"		//void john(); の定義部はコンパイルされない
#include "paul.hpp"		//void paul(); の定義部はコンパイルされない
#include "johnpaul.hpp"

void johnpaul()
{
	john();		//宣言されてない
	paul();		//宣言されてない
}

の、john(); paul();関数が見つからないからだ。
ということがわかってよかった。よしよし。