条件付きコンパイル①
条件付きコンパイル、プリプロセッサは、コンパイラの違いを吸収したり、テストコードを残したり、同じソースをプロジェクト毎に使い分けたり、コメントアウト(//や/**/、/**/は入れ子が出来ないので)の代わりに使ったりと、どのプロジェクトでも大活躍だと思いますが、正確に使い分けられていなかったり、またソースの可読性を著しく低下させたりする事からバグの温床となる事が良くあります。ここでは#if, #ifdef, #if defined, この三種類のプリプロセッサ命令の違いを詳細に見ていきたいと思います。
例1.プリプロセッサ条件付きコンパイル
#include <stdio.h> #define YELLOW 0 #define BLUE 1 #define LED //#define GREEN void func(int num){ printf("%d\n", num); } int main(){ #if YELLOW //偽 func(1); #endif #if (YELLOW) //偽 func(2); #endif //#if LED // fatal error C1017: 整数定数式が無効です。 // func(3); //#endif #if GREEN //偽 func(4); #endif #ifdef YELLOW //真 func(5); #endif //#ifdef (YELLOW) //fatal error C1016: #if[n]def は、識別子を必要とします。 // func(6); //#endif #if defined YELLOW //真 func(7); #endif #if defined (YELLOW) //真 func(8); #endif #if defined (LED) //真 func(9); #endif #if defined (GREEN) //偽 func(10); #endif }
#defineの違いに注目してください。YELLOWはシンボルが定義され、その値が0と定義されています。BLUEはシンボルが定義され、その値が1と定義されています。LEDはシンボルだけ定義されており、GREENはシンボルも値も未定義です。
#ifは続く値、もしくはシンボルの値が真か偽を評価しますが、#ifdefと#if definedは続くシンボルそれ自体が定義されているかいないかを評価するという違いがあります。なお0が偽、0以外が真となります。
例ではYELLOWの値が0と定義されている為、#if YELLOWの評価は偽となります、一方、#ifdef YELLOW, #if defined YELLOWではYELLOWというシンボルの定義はありますので、その値が0だろうが1だろうが評価が真となります。また#if LEDはLEDに値が定義されていない為、LEDを評価できずプリコンパイル時にエラーとなります。一方、#ifdef LED, #if defined LEDではシンボル自体は定義されているので真になります。#if GREENはシンボルすら定義されていないのでプリコンパイルでエラーとはならず偽となります、もちろん#ifdef GREEN, #if defined GREENでもGREENが未定義なので偽となります。
ここまで見ると#ifdefと#if definedは同じ仕様のようですが、#ifdefで評価するシンボルは()括弧をつけるとエラーとなりますので注意が必要です。一方、#ifと#if definedは()括弧をつけても評価結果は変わりません、むしろセキュアコーディングにおいてはマクロに()括弧をつける事を推奨していますので(関数マクロなどでは括弧をつけないと意図しない結果を返すことがあるため)#ifと#if definedにつづく値、シンボルには積極的に()括弧をつけていくのが良いかと思います。
まとめ
①#ifは値もしくはシンボルの値の真偽を評価し、#ifdef, #if definedはシンボルそれ自体の定義有無を評価する。
②#ifは値を持たないシンボルを評価できないが、#ifdef, #if definedはシンボルの値があってもなくても値は無視して定義有無だけを評価する。
③#ifdef は評価対象に括弧をつけてはならないが、 #if, #if definedは評価対象に積極的に括弧をつける事が推奨される。