プリプロセッサでプログラムの質を向上させよう:目指せ! Cプログラマ(16)(4/4 ページ)
プログラミング言語Cの強力な機能の1つに、「プリプロセッサ」があります。正しく使えば、間違いが少なくて、意味も伝わりやすいプログラムを、より簡単に書くことができます。プリプロセッサを使いこなして、プログラムの質をぐっと向上させましょう。
定義済みマクロ
いくつかの便利なマクロについては、言語仕様であらかじめ定義されています。これらのマクロは、コンパイルを実行したときに自動的に値が設定されます。
ここでは「日時に関するマクロ」「ソースファイルに関するマクロ」「STDCで始まるマクロ」の3つに分けて説明します。
日時に関するマクロ
日時に関するマクロは、プリプロセッサが起動された日付を表す「__DATE__」マクロと、同じく時刻を表す「__TIME__」マクロの2つです。
「__DATE__」は「月の英語名(3文字) 日 年」の形式の文字列になります。例えば2013年1月1日であれば「Jan 1 2013」です。「__TIME__」は「時:分:秒」の形式の文字列になります。
これらは、コンパイルした日時をプログラム内に埋め込んだりするのに使えます。バイナリコードの生成時刻を何らかの方法で表示できるようにしておくと便利でしょう。
ソースファイルに関するマクロ
ソースファイルに関するマクロは、ソースファイルのファイル名を表す「__FILE__」マクロと、ファイル中の行番号(その位置までに読み込んだ改行文字の数+1)を表す「__LINE__」マクロです。デバッグなどで表示するときに使えます。
あまり使われることはありませんが、ファイル名や行番号はプリプロセッサの「#line」指令を使って変更することもできます。「#line 行番号」で行番号を、「#line 行番号 "ファイル名"」で、行番号とソースファイル名を変更できます。
またC99からは「__func__」識別子が用意されました。これは厳密にはマクロではなく識別子ですが、定義せずともどの関数からでも参照でき、参照した場所の関数の名前が入っているため、同じようにデバッグに使えます。C99より前の規格に準拠したコンパイラでは使えませんが、独自拡張として「__FUNCTION__」や「__FUNC__」などの名前で同様のマクロが定義されていることがあります。
STDCで始まるマクロ
「STDC」で始まるマクロを使うと、コンパイラがCの標準仕様に準拠しているかどうか、あるいはどのバージョンに準拠しているかの判定といったことができるようになります。ここではいくつか紹介します。
「__STDC__」マクロは、コンパイラがCの規格に準拠していれば定義されます。規格に準拠したOS環境の場合には「__STDC_HOSTED__」マクロが「1」(ホスト実行環境)に、そうでなければ「0」(フリースタンディング実行環境)になります。
また、「__STDC_VERSION__」マクロの定義状況を見ると、コンパイラがどのバージョンに準拠しているかが分かります。定義されている場合、値の型は「long int」になります。
ISO/IEC 9899/AMD1:1995(C89+多バイト拡張)より前(C89など) | 未定義 |
---|---|
ISO/IEC 9899/AMD1:1995(C89+多バイト拡張) | 「199409L」 |
ISO/IEC 9899:1999(C99) | 「199901L」 |
ISO/IEC 9899:2011(C11) | 「201112L」 |
さらに、コンパイラが備えている機能によって次のマクロも定義されます。
__STDC_IEC_559__ | IEC60559浮動小数点演算の規定に準拠している場合は1 |
---|---|
__STDC_IEC_559_COMPLEX__ | IEC60559互換複素数演算の規定に準拠している場合は1 |
__STDC_ISO_10646__ | wchar_tがISO/IEC10646(UCS)準拠の符号化表現を持つ場合、その規格の年月(「201103L」など) |
その他の定義済みマクロ
C++は、Cに対してある程度の上位互換を持っています。非互換の部分を避けるようにして注意深くプログラムを書けば、CでもC++でも使えるようなプログラムにすることは可能です。しかし、CとC++のコードを1つのファイルに混在させた方が良いケースも出てきます。そんなときには「__cplusplus」マクロを使って、C用とC++用の両方のコードを分けて書くことができます。
#ifdef __cplusplus // C++のコード #else // Cのコード #endif
また、コンパイラごとにコードを書き分けるため、通常はコンパイラや実行環境を識別するためのマクロが定義されています。代表的な例を以下に挙げました。
Visual C++の場合
_MSC_VER | コンパイラのバージョン |
---|---|
_WIN32 | Win32またはWin64用アプリケーションの場合 |
_WIN64 | Win64用アプリケーションの場合 |
GCCの場合
__GNUC__ | コンパイラのメジャーバージョン |
---|---|
__GNUC_MINOR__ | コンパイラのマイナーバージョン |
__GNUC_PATCHLEVEL__ | コンパイラのパッチレベル |
プラグマ
「プラグマ」という機能を使うと、コンパイラに特別な指示を与えることができます。
プラグマの書き方は、(1)「#pragma」指令を使う方法と、(2)「_Pragma」演算子を使う方法の2種類があります。基本的にはどちらもプリプロセッサで処理され、機能は同じですが、後者はC99でサポートされ、用途によってはこの方が見やすいコードが書けます。
#pragma指令を使う場合は「#pragma 文字列」のように書き、他の前処理と同じで行頭に置きます。
_Pragma演算子を使う場合は、「_Pragma("文字列")」のように書きます。行頭になければいけない、といった制限はありません。ただし文字列リテラルと同じように、ダブルクオートが入る場合にはエスケープする必要があります。ダブルクオートを含む文字列を指定することはあまりないでしょうが、文法事項として理解しておくために「He said "Yes."」という文字列を指定する例を紹介しておきます。次のようになります。
_Pragma("He said \"Yes.\"")
コンパイラごとにさまざまなプラグマが用意されていて、よく使われるものを紹介しておきます。なお、使用できるプラグマについてはコンパイラによって変わりますから、実際に使う場合はコンパイラのドキュメントを確認するようにしましょう。
#pragma once
条件付き取り込みで説明した、ヘッダの2重取り込みを防止します。Visual C++やGCCなど、主要なコンパイラでサポートされています。「#ifndef」を使うよりもコンパイル時間が短くなる場合があるようです。
#pragma warning
コンパイル警告を抑制します。Visual C++で使えます。GCCなどの他のコンパイラでも、別の形式でサポートされています。
#pragma pack(n)
構造体や共用体などのアライメント(オブジェクトを配置するメモリ境界)を指定します。Visual C++やGCCなど、主要なコンパイラでサポートされていますが、書式が異なる場合があります。
なお、Cの規格では、「STDC FP_CONTRACT」「STDC FENV_ACCESS」「STDC CX_LIMITED_RANGE」の3つが用意されています。入門者のうちは使うことはないでしょうが、他の人が書いたコードを読んだときに出てくるかもしれません。それぞれどういった指示となるかについては、処理の高速化が必要になったときに調べてみれば良いでしょう。
その他の指令
これまでに説明した指令(ディレクティブ)の他に、次のものがあります。
#errorメッセージ
この指令が有効な場合、コンパイラは指定されたメッセージを表示し、コンパイルを失敗させます。
例えばマクロ「AA」とマクロ「BB」が同時に定義されていてはいけないとすると、次のように書くことができます。
#if defined(AA) && defined(BB) #error マクロAAとマクロBBは同時に定義できません #endif
> cc -DAA -DBB test.c test.c: error: マクロAAとマクロBBは同時に定義できません
#(空指令)
行に「#」だけが単独で現れる場合、空指令となり、何も影響はありません。
今回学んだこと
- 前処理はコンパイラがコンパイル前に起動するプリプロセッサで行われます
- 行頭の「#」から始まる指令(ディレクティブ)で前処理を書きます
- ヘッダ取り込みは「#include」で行います
- マクロは「#define」で定義し、ソースコード中に現れる識別子が置き換えられます
- マクロ置き換えでは「#」演算子や「##」演算子が使えます
- 条件付き取り込みでは「#if」や「#ifdef」などを使って、コードの有効/無効を切り替えられます
- 「#pragma」または「_Pragma」演算子を使ってコンパイラに指示を与えられます
Copyright © ITmedia, Inc. All Rights Reserved.