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

先頭へ戻る

この章の概要

この章の概要です。

事前定義マクロ

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

C99 で、__STDC_HOSTED__ が追加されています(後述)。

__FILE__

__FILE__は、このマクロを使用したソースファイルの名前を表す文字列リテラルに置換されます。

#include <stdio.h>

int main(void)
{
    puts( __FILE__ );

    return 0;
}

実行結果:

main.c

__FILE__ によって置換された結果は文字列リテラルですから、puts関数に渡せばそのまま出力できます。

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

__LINE__

__LINE__は、このマクロを使用したファイル上の行の番号を表す整数型の定数に置換されます。

#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 に置換されます。 準拠していない場合には、そもそもこのマクロが定義されていません。 0 などの値に置換される訳ではないので、使い方に注意して下さい。

#include <stdio.h>

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

    return 0;
}

実行結果 (VisualStudio):

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

実行結果 (clang):

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

VisualStudio、clang とで実行結果が異なっています。 VisualStudio での結果は意外かも知れません。

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

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

__STDC_VERSION__

__STDC_VERSION__ は、コンパイラがどの標準規格に対応しているかを表す、long int型の定数に置き換わります。

以下の表のようになっています。

対応規格 結果
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;
}

実行結果 (VisualStudio):

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

実行結果 (clang):

201112

VisualStudio では、__STDC__ の定義の有無とも関係なく、常に __STDC_VERSION__ は定義されないようです。
clang では、__STDC_VERSION__ が定義されているので、値が出力されます。

VisualStudio ではできませんが、本来的には、__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 (__STDC_HOSTED__)

C99 で、事前定義マクロに __STDC_HOSTED__が加わりました。このマクロは、コンパイラが、ホスト処理系であれば 1 に、フリースタンディング処理系であれば 0 に置換されます。

ホスト処理系とは、プログラムの実行が OS の支援によって行われる環境のことです。 このような環境では、C言語の標準規格に存在するすべての機能が使用できます。

フリースタンディング処理系とは、OS の支援無しでプログラムが実行される環境で、 C言語の標準規格に存在する機能の一部しか使用できません。

#include <stdio.h>

int main(void)
{
    printf( "%d\n", __STDC_HOSTED__ );

    return 0;
}

実行結果 :

1

(C99) 事前定義識別子

C99 では、事前定義識別子が追加されています。 これは、事前定義マクロと違って、(そのままですが)マクロではありません。 いわば、変数を暗黙的に宣言しているのと似ており、スコープの概念があります。

C99 で定義されている事前定義識別子は __func__ だけです。

C99 (__func__)

__func__は、関数の定義内でのみ使用でき、その関数名の文字列リテラルを得られます。より具体的には、関数の冒頭部分に、以下のコードがあるかのようにコンパイルされます。

static const char __func__[] = "関数名";

使用例は以下のようになります。

#include <stdio.h>

void func(void);

int main(void)
{
    puts( __func__ );

    func();

    return 0;
}

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

実行結果:

main
func

#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行目になりそうに見えるかも知れませんが、そうはなりません。 プリプロセッサディレクティブは、必ず1行使ってしまうので、こういう仕様でないと困りますが。

行番号に続けて、文字列リテラルも記述した場合には、その内容をファイルの名前と認識し、__FILE__ の置換結果を変更します。 ファイル名を指定しない場合は、現在の名称のまま変更しません。 なお、ファイル名だけを変更することはできません。

#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;
}

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

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

clang 5.0.0 の場合も、同様の意味のエラーメッセージが出力されます。

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

#pragma指令

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

#pragma 命令

#pragma という機能自体は標準規格で定められたものですが、「命令」のところに来るものは完全にコンパイラ依存のものです。 ですから、どんなことができるのかは、コンパイラのマニュアル等を参照するしかありません。

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

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

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

#ifndef MY_HEADER_H
#define MY_HEADER_H

/* ヘッダの本体 */

#endif

と書く代わりに、次のように記述します。

#pragma once

/* ヘッダの本体 */

このようにヘッダファイルの先頭に記述しておくだけで、インクルードガードと同等のことが行なえます。 こちらの方が、記述量が減り、マクロ名が衝突することを考慮しなくてよくなりますが、 コンパイラの種類によっては動作しない可能性があります。


練習問題

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

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

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

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


解答ページはこちら

参考リンク

更新履歴

'2018/4/23 「C99 (__func__)」の項の内容を修正。より具体的な動作を追記。VisualStudio 2015 で使用できないという記述を削除(使用できる)

'2018/4/22 解説中で C95 を(C89 に対して)特別扱いしないように修正。そもそもC言語編は C95ベースなので、余計な説明は省く。

'2018/4/5 VisualStudio 2013 の対応終了。

'2018/4/2 「VisualC++」という表現を「VisualStudio」に統一。

'2018/3/5 全面的に文章を見直し、修正を行った。
C99 (__STDC_HOSTED__)」の項を追加。
#error指令」と「空指令」の項を、第24章へ移動。

≪更に古い更新履歴を展開する≫





前の章へ(第28章 関数形式マクロ)

次の章へ(第30章 理解の定着・小休止③)

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

Programming Place Plus のトップページへ


このエントリーをはてなブックマークに追加
rss1.0 取得ボタン RSS