C言語編 第29章 事前定義マクロとプラグマ

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

事前定義マクロ

何らかのヘッダファイルをインクルードしなくても使える、あらかじめ定義されているマクロを、事前定義マクロといいます。 C95 規格では、事前定義マクロは以下の6種類が存在します。

C99 では、「__STDC__」で始まる名称のマクロが幾つか追加されています。

__FILE__

__FILE__は、このマクロを記述しているファイルの名前に置換されます。

#include <stdio.h>

int main(void)
{
	puts( __FILE__ );
	
	return 0;
}

実行結果:

main.c

__FILE__ によって置換された結果は、普通の文字列ですから、puts関数(⇒リファレンス)に渡せばそのまま出力できます。 もちろん、printf関数(⇒リファレンス)の %sフォーマット指定でも構いません。

なお、ファイル名を強制的に変更する#line指令という命令が存在します。 これは後で説明します。

__LINE__

__LINE__は、このマクロを記述している行の行番号に置換されます。 置換された結果は整数になり、printf関数(⇒リファレンス)で出力するとすれば、%dフォーマット指定を使います。

#include <stdio.h>

int main(void)
{
	printf( "%d\n", __LINE__ );
	
	return 0;
}

実行結果:

5

なお、行番号を強制的に変更する#line指令という命令が存在します。 これは後で説明します。

__DATE__

__DATE__は、コンパイルを行ったときの日付に置換されます。 置換された結果は文字列です。

#include <stdio.h>

int main(void)
{
	puts( __DATE__ );
	
	return 0;
}

実行結果:

Sep 12 2009

実行結果から分かるように、日付の形式は「月 日 年」の順番であり、間を半角スペースで空けた状態です。 この形式を変更することはできません。
なお、これらの表現形式は asctime関数(⇒リファレンス)が返すものと同様です。 asctime関数は、第43章で説明します。

__TIME__

__TIME__は、コンパイルを行ったときの時刻に置換されます。 置換された結果は文字列です。

#include <stdio.h>

int main(void)
{
	puts( __TIME__ );
	
	return 0;
}

実行結果:

13:28:16

時刻の形式は「時:分:秒」の順番で、間は : で接続されています。 24時間形式で表現され、それぞれ 2桁の 10進数です。
__DATE__ の置換結果の表現と同様、__TIME__ の表現も asctime関数(⇒リファレンス)と同じになります。

__STDC__

__STDC__は、コンパイラが標準規格に準拠しているかどうかを調べます。 規格に準拠していれば、1 に置換されます。 準拠していない場合には、そもそもこのマクロが定義されていません。

#include <stdio.h>

int main(void)
{
#ifdef __STDC__
	puts( "このコンパイラは規格に準拠しています。" );
#else
	puts( "このコンパイラは規格に準拠していません。" );
#endif
	
	return 0;
}

実行結果 (VisualC++):

このコンパイラは規格に準拠していません。

実行結果 (clang):

このコンパイラは規格に準拠しています。

VisualC++、clang とで実行結果は異なります。 最近の多くのコンパイラは、C++コンパイラでもあり、こういうコンパイラが __STDC__ を定義しているかどうかは不明確です。

VisualC++ では、プロジェクトのプロパティを開き、「構成プロパティ」→「C/C++」→「言語」を選択し、 「言語拡張を無効にする」を「はい(/Za)」に変更すれば、__STDC__ が定義されます。

C99 では、「__STDC__」で始まる名前の事前定義マクロが幾つか追加されており、より正確に規格準拠の度合いを調べることができるようになっています。

__STDC_VERSION__

__STDC_VERSION__ は、コンパイラがどの規格に対応しているかを表す値に置き換わります。

対応規格 結果
C95 199409L
C89以前、または規格に非対応 このマクロが定義されていない
#include <stdio.h>

int main(void)
{
#ifdef __STDC_VERSION__
	printf( "%ld\n", __STDC_VERSION__ );
#else
	printf( "__STDC_VERSION__ が定義されていません。\n" );
#endif
	
	return 0;
}

実行結果 (VisualC++):

__STDC_VERSION__ が定義されていません。

実行結果 (clang):

199901

VisualC++ では、__STDC__ の定義の有無とも関係なく、常に __STDC_VERSION__ は定義されないようです。
clang 3.7 のデフォルトの状態では、__STDC_VERSION__ が定義されているので、値が出力されます。

clang 3.7 においては、-std=c89 のようにコンパイラオプションを与えることで、C89規格に準拠したコンパイルが行われるので、 その場合には、「__STDC_VERSION__ が定義されていません。」と出力されます。 Xcode 7.3 上では、【Build Settings】→【Apple LLVM 7.1 - Language】→【C Language Dialect】を【C89 [-std=c89]】とすれば確認できます。同様に、最後の項目を【c11】とすれば、C11規格を使うようになります。

VisualC++ ではできませんが、本来的には、__STDC__ の結果と、__STDC_VERSION__ の結果とを組み合わせて、 対応規格を知ることができます。

__STDC__ が定義されていて、__STDC_VERSION__ が 199409L C95規格に対応
__STDC__ が定義されていて、__STDC_VERSION__ が定義されていない C89規格に対応
__STDC__ が定義されていない。 C言語の規格に合致していない。

C99 (__STDC_VERSION__ の値)

C99 では、__STDC_VERSION__ は 199901L になります。

C11 (__STDC_VERSION__ の値)

C11 では、__STDC_VERSION__ は 201112L になります。

事前定義識別子 (C99)

C99 (__func__)

C99 では、事前定義識別子(既定義識別子)が追加されています。 これは、事前定義マクロと違い、スコープの概念を持っており、ローカル変数と同じような使い方ができます

C99 で定義されている事前定義識別子は __func__ だけです。 これは、関数内で使用でき、その関数の名前を得ることができます

#include <stdio.h>

void func(void);

int main(void)
{
	puts( __func__ );
	
	func();

	return 0;
}

void func(void)
{
	puts( __func__ );
}

実行結果:

main
func

VisualC++ 2013/2015 では使用できませんが、2017 では使用できます。 ただし、__FUNCTION__ に置き換えれば同じ結果を生みます(Microsoft の独自拡張です)。この拡張機能は 2013/2015/2017 のいずれでも使用できます。
clang 3.7 では使用できます

#line指令

#line指令は、行番号とファイル名を強制的に変更する効果があり、__FILE__ と __LINE__ の置換結果に影響を与えます。

#include <stdio.h>

int main(void)
{
#line 1000
	printf( "%s %d\n", __FILE__, __LINE__ );

#line 2000 "test.c"
	printf( "%s %d\n", __FILE__, __LINE__ );

#line 3000
	printf( "%s %d\n", __FILE__, __LINE__ );
	
	return 0;
}

実行結果:

main.c 1000
test.c 2000
test.c 3000

#line で指定された行番号が、その次の行の行番号になります。 ぱっと見だと、上のプログラムの printf関数を呼び出している行は、1001行目、2001行目、3001行目になりそうに見えますが、そうはなりません。

行番号に続けて、文字列も記述した場合には、ファイルの名称も変更されます。 ファイル名を指定しない場合は、現在の名称のまま変更しません。 なお、ファイル名だけを変更することはできません。

#line が確実に効果をもたらす対象は、__FILE__ と __LINE__ の置換結果だけですが、 大抵は、コンパイラが出力するエラーメッセージなどにも影響を与えます。 先ほどのサンプルプログラムに、わざとコンパイルエラーを起こさせて試してみます。

#include <stdio.h>

int main(void)
{
#line 1000
	printf( "%s %d\n", __FILE__, __LINE__ );

#line 2000 "test.c"
	printf( "%s %d\n", __FILE__, __LINE__ );
	a + 1;

#line 3000
	printf( "%s %d\n", __FILE__, __LINE__ );
	
	return 0;
}

VisualC++2017 の場合、次のようなエラーメッセージを出力しました。

1>test.c(2001) : error C2065: 'a' : 定義されていない識別子です。

clang 3.7 では、次のようなエラーメッセージが出力されました。

test.c:2001:2: error: use of undeclared identifier 'a'

ファイル名が test.c に、行番号が 2001 になっているので、確かに #line の影響を受けているようです。

#error指令

#error指令は、指定されたメッセージを出力し、コンパイル作業を中止します。

#include <stdio.h>

/* 下の2つは、いずれか一方だけを有効にすること */
#define DEBUG_MODE          /* デバッグモードでコンパイル */
#define RELEASE_MODE        /* リリースモードでコンパイル */

int main(void)
{
#if defined(DEBUG_MODE) && defined(RELEASE_MODE)
	#error "DEBUG_MODE and RELEASE_MODE can define only either of them."
#endif

	puts( "OK" );
	
	return 0;
}

#error の箇所がコンパイルされると、コンパイルエラーになり、指定した文字列がエラーメッセージとして出力されます。

このサンプルのように、同時に定義されていてはならないマクロが定義されている場合や、 定義されていなければならないマクロが定義されていない場合、 置換結果が適切でない場合など、コンパイルの段階で検出できるエラーを調べるために役に立ちます。

複数のコンパイラをサポートするソースコードを作成している場合に、コンパイラの種類やバージョンを調べるのにも使われます。 普通、コンパイラメーカーはそれぞれの製品に、自身の情報を表すマクロ(製品の種別やバージョン番号など)を定義しています。

空指令

#記号から始まる行は、プリプロセッサが処理を行う訳ですが、#記号だけしか存在しない行も同様です。 この場合、何も行われることはないので、空指令と呼びます。
空指令は、#if - #elif のような並びが連続する場合など、ソースコードが見づらくなりがちな場合に、行間を空ける目的で利用できます。

#ifdef SIGNED
#
#define BYTE_MAX  127
#define BYTE_MIN  -128
#
#else
#
#define BYTE_MAX  255
#define BYTE_MIN  0
#
#endif

また、#記号の前後に空白やタブを置くことは許可されています。

#ifdef SIGNED
#	define BYTE_MAX  127
#	define BYTE_MIN  -128
#else
#	define BYTE_MAX  255
#	define BYTE_MIN  0
#endif

プログラムを見やすく書くことは、当然ながら、プリプロセスの部分でも例外ではありません。 このように書き方を少し工夫することはできます。

pragma指令

pragma指令は、コンパイラへ何らかの命令を与える手段として用意されています。 形式としては、次のようになります。

#pragma 命令

「命令」の部分に来る内容は、コンパイラによって異なります。 つまり、#pragma は完全に環境依存の機能です。 どんなことができるのかは、コンパイラのマニュアル等を参照するしかありません。

C99 では、幾つか標準の pragma指令が追加されました。

「命令」の部分に記述した内容を、コンパイラが理解できる場合は、その意味通りに処理されます。 「命令」を理解できない場合は、完全に無視されます
もし、複数のコンパイラに対応したプログラムを作るのなら、必要に応じて、#ifdef で切り分けるなどしないと、 うまく動作しないでしょう。

一応、#pragma の例を挙げておきます。 VisualC++ や clang では、#pragma once という指令を使うと、インクルードガード(第24章)を行えます。

#ifndef MY_HEADER_H
#define MY_HEADER_H

/* ヘッダの本体 */

#endif

と書く代わりに、

#pragma once

/* ヘッダの本体 */

記述量が随分と減り、マクロ名の衝突を考慮する必要もなくなりますが、 #pragma なので、コンパイラの種類によっては動作しない可能性があります。


練習問題

問題@ プログラムの実行直後に、そのソースファイルをコンパイルしたときの日付・時刻を出力してみて下さい。

問題A 現在のソースファイル名と行数を出力するデバッグ関数を次のように作成しました。 しかし、この関数には実用上の問題があります。問題点を指摘して下さい。

void printLog()
{
	printf( "File: %s   Line: %d\n", __FILE__, __LINE__ );
}

問題B 自分の使っているコンパイラに用意されている pragma指令について調べて下さい。


解答ページはこちら

参考リンク

更新履歴

'2017/6/7 「C11 (__STDC_VERSION__ の値)」を追加。

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に、Xcode のバージョンを 7.3 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/10/18 clang 3.2 に対応。

'2014/3/15 clang でも #pragma once は使えるようなので、記述を改めた。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/14 VisualC++ 2008 の対応終了。

'2014/1/11 clang 3.0 に対応。

'2013/4/27 「事前定義識別子 (C99)」の項を追加。

'2013/4/14 __STDC_VERSION__ についての記述を追加。

'2011/9/17 VisualC++2012 での結果を確認。

'2010/5/1 VisualC++2010 での結果を確認。

'2009/9/20 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ