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

トップページC言語編

このページの概要

以下は目次です。


事前定義マクロ

なんらかのヘッダファイルをインクルードしなくても使える、あらかじめ定義されたマクロのことを、事前定義マクロ (predefined macro) といいます。事前定義マクロは多数ありますが、代表的なものを取り上げると以下があります。

__FILE__

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

// main.c
#include <stdio.h>

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

実行結果:

main.c

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

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

__LINE__

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

#include <stdio.h>

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

実行結果:

5

なお、行番号を強制的に変更する #line指令という命令があり、その影響を受けます。これは後で説明します

__DATE__

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

#include <stdio.h>

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

実行結果:

Mar 11 2023

置換結果は、日付の形式は「月 日 年」の順番であり、間を半角スペースで空けたものです。「月」の部分は asctime関数 と同じ表現になります(asctime関数は、第51章で取り上げます)。「日」の部分は必ず2桁分の幅を取り、10未満のときは上位桁が空白文字になります1

__TIME__

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

#include <stdio.h>

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

実行結果:

13:28:16

時刻の形式は「時:分:秒」の順番で、: で区切られています。24時間形式で表現され、それぞれ 2桁の 10進数です。

__DATE__ と同様に、__TIME__ の表現形式も asctime関数第51章)と同じです。

__STDC__

__STDC__は、処理系が標準規格に準拠していれば 1 に置換されます。準拠していない場合には、そもそもこのマクロが定義されていません。0 などの値に置換されるわけではないので、使い方に注意してください。

#include <stdio.h>

int main(void)
{
#ifdef __STDC__
    puts("Conforms to standards.");
#else
    puts("Not standard compliant.");
#endif
}

実行結果 (Visual Studio):

Not standard compliant.

実行結果 (clang):

Conforms to standards.

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

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

__STDC_VERSION__

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

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

対応規格 結果
C99 199901L
C95 199409L
C89以前、または規格に非対応 このマクロが定義されていない

【C11】__STDC_VERSION__ の置換結果は 201112L に更新されました。

#include <stdio.h>

int main(void)
{
#ifdef __STDC_VERSION__
    printf("%ld\n", __STDC_VERSION__);
#else
    printf("__STDC_VERSION__ undefined.\n");
#endif
}

実行結果 (Visual Studio):

__STDC_VERSION__ undefined.

実行結果 (clang):

201112

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

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

__STDC__ __STDC_VERSION__ 意味
定義されている 199901L C99規格に対応
定義されている 199409L C95規格に対応
定義されている 定義されていない C89規格に対応
定義されていない C言語の標準規格に合致していない

__STDC_HOSTED__

__STDC_HOSTED__ は、処理系が、ホスト処理系であれば 1 に、フリースタンディング処理系であれば 0 に置換されます。

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

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

#include <stdio.h>

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

実行結果 :

1

事前定義識別子

事前定義マクロと非常によく似ているものに、事前定義識別子 (predefined identifier) があります。名前のとおりですが、事前定義マクロと違ってマクロではなく、識別子です。いわば、変数を暗黙的に宣言しているのと似ており、スコープの概念があります。

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

__func__

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

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

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

#include <stdio.h>

void func(void);

int main(void)
{
    puts(__func__);

    func();
}

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

実行結果:

main
func

#line

#line は、ソースファイルの行番号や名前を制御します。

#line の使い方は次の2通りあります。

#line 行番号
#line 行番号 ソースファイル名

いずれにしても、#line を記述した行の行番号を「行番号」に指定した数とみなすようにプリプロセッサに指示を与えます。「ソースファイル名」を指定した場合は、同様に #line を記述した行以降のソースファイル名を「ソースファイル名」とみなすようになります。その結果、__FILE__ や __LINE__ の置換結果が影響を受けます。また、処理系がエラーや警告などを報告するときに表示されるソースファイル名や行番号にも影響することがあります。

#include <stdio.h>

int main(void)
{
    puts(__FILE__);
    printf("%d\n", __LINE__);

#line 1000 "new_name.c"
    puts(__FILE__);
    printf("%d\n", __LINE__);
}

実行結果:

c:\test_program\main.c
6
new_name.c
1001

プラグマ

最後にプラグマ (pragma) を紹介します。プラグマは、処理系が独自に実装している、標準でない機能を使うための構文で、次のように #pragma を使って記述します。

#pragma *****

***** の部分は、使おうとする機能によって異なります。標準のものではないため、処理系のドキュメントを確認するなどして理解しなければなりません。

【上級】解説は省きますが、#pramga を #define の「置換後の文字の並び」のところで使えない問題を解決する方法として、_Pragma演算子があります2

比較的よく認知されているプラグマとして、#pragma once があります。これはインクルードガード(第24章)を簡単に実現します。ヘッダファイルの先頭に #pragma once と記述するだけで、インクルードガードの役割を果たします。

#pragma once

// ほかのコード

主要なコンパイラではインクルードガードとして機能しますが、あくまでも処理系定義の機能です。これを認識しないコンパイラでは、単にこの記述を無視するかもしれません。


練習問題

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

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

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

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


解答ページはこちら

参考リンク


更新履歴

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



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

次の章へ (第30章 アサート)

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

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る