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

トップページC言語編

このページの概要 🔗

このページの解説は C99 をベースとしています

以下は目次です。


事前定義マクロ 🔗

なんらかのヘッダファイルをインクルードしなくても使える、あらかじめ定義されたマクロのことを、事前定義マクロ (predefined macro) といいます。

事前定義マクロを #undef で無効にすることはできません。

事前定義マクロは多数存在していますが、ここでは代表的なものだけを取り上げます。ここで取り上げていない事前定義マクロについては、APPENDIX >「事前定義マクロの一覧」を参照してください。

__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 に置換されます。準拠していない場合にどうなるべきなのかは不明です(それを定義していても、それに準拠していないわけですし)。

#include <stdio.h>

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

実行結果(Visual Studio):

Not standard compliant.

実行結果(gcc):

Conforms to standards.

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

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

__STDC_VERSION__ 🔗

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

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

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

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

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

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

#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.

実行結果(gcc 6.4.0 (MinGW-w64)):

201112

Visual Studio では、__STDC__ の定義の有無とも関係なく、つねに __STDC_VERSION__ は定義されないようです。gcc では、__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指令について調べてください。


解答ページはこちら

参考リンク 🔗


更新履歴 🔗

≪さらに古い更新履歴≫

 「C11(__STDC_MB_MIGHT_NEQ_WC__)」の項を追加。

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

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

 VisualStudio 2013 の対応終了。

 「VisualC++」という表現を「VisualStudio」に統一。

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

 C言語編全体で表記を統一するため、「フォーマット指定」を「変換指定子」に改めた。

 コンパイラの対応状況について、対応している場合は明記しない方針にした。
Xcode 8.3.3 を clang 5.0.0 に置き換え。

 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

 「C11 (__STDC_VERSION__の値)」を追加。

 VisualC++ 2017 に対応。

 clang の対応バージョンを 3.7 に、Xcode のバージョンを 7.3 に更新。

 clang の対応バージョンを 3.4 に更新。

 VisualC++ 2012 の対応終了。

 VisualC++ 2010 の対応終了。

 VisualC++ 2015 に対応。

 clang 3.2 に対応。

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

 VisualC++ 2013 に対応。

 VisualC++ 2008 の対応終了。

 clang 3.0 に対応。

 「事前定義識別子 (C99)」の項を追加。

 __STDC_VERSION__ についての記述を追加。

 VisualC++2012 での結果を確認。

 VisualC++2010 での結果を確認。

 新規作成。



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

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

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

Programming Place Plus のトップページへ



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