お久しぶりの初心者Cプログラマーです。いつから初心者を脱するのか不明ですが初心者です。今回ちょっとした小ネタ投稿。
#include
ディレクティブ? 2種類でしょ?
Cプログラマーであればおよそ誰もが使う1#include
。概ね以下の2つであろうと思います。
// <>で囲むやつ
#include <stdio.h>
// ""で囲むやつ
#include "private_use.h"
はい、普通これでOKですが、第3の形式があります。
マクロ展開によるパターン増
それがこれです。
#define HEADER_MACRO "switched.h"
#include HEADER_MACRO
すべてプリプロセス時点で完結するからできるのかもしれませんが、こんな誰が使うのか分からない指定ができるわけです。
どうやって使うのか?
具体的に何ができるかというと、以下のようなことができます。
#ifndef INCLUDE_VER1_H_INCLUDE
#define INCLUDE_VER1_H_INCLUDE
#include <stdio.h>
#define MSG_FUNC(fmt, ...) printf("LOG ver1: " fmt "\n", ##__VA_ARGS__)
#endif
#ifndef INCLUDE_VER2_H_INCLUDE
#define INCLUDE_VER2_H_INCLUDE
#include <stdio.h>
#define MSG_FUNC(fmt, ...) printf("LOG ver2: " fmt "\n", ##__VA_ARGS__)
#endif
#ifdef USE_VER1
#define INCLUDE_FILE "include_ver1.h"
#else
#define INCLUDE_FILE "include_ver2.h"
#endif
#include INCLUDE_FILE
int main(void)
{
MSG_FUNC("use header %s", INCLUDE_FILE);
}
見れば分かるかと思いますが、USE_VER1
が定義されていればinclude_ver1.h
を、そうでなければinclude_ver2.h
をインクルードしようとしています。これが機能すれば、USE_VER1
定義ありの場合はMSG_FUNC
で"LOG ver1: "が、定義なしなら"LOG ver2: "が出力されるはずです。
手元のWSL2/ubuntu20.04環境で実行したところ、以下のような実行結果が得られました。
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ clang --version
clang version 10.0.0-4ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ gcc -Wall -Wextra include_test.c
$ ./a.out
LOG ver2: use header include_ver2.h
$ gcc -Wall -Wextra -D USE_VER1 include_test.c
$ ./a.out
LOG ver1: use header include_ver1.h
$ clang -Wall -Wextra include_test.c
$ ./a.out
LOG ver2: use header include_ver2.h
$ clang -Wall -Wextra -D USE_VER1 include_test.c
$ ./a.out
LOG ver1: use header include_ver1.h
適用されていそうです。
それC言語じゃなくてプリプロセッサが気を利かせただけじゃないの?
と我に返る方もいるでしょうから、規格を見ましょう。いつものn1570(C11のdraft)2です。
6.10.2-4
A preprocessing directive of the form
# include pp-tokens new-line
(that does not match one of the two previous forms) is permitted. The preprocessing tokens after include in the directive are processed just as in normal text. (Each identifier currently defined as a macro name is replaced by its replacement list of preprocessing tokens.) The directive resulting after all replacements shall match one of the two previous forms. ...
拙訳:
# include プリプロセストークン 改行
この形式の(前の2形式のいずれにも該当しない)プリプロセス命令が許容される。命令内でinclude
の後に置かれるプリプロセストークンは、通常のテキストのようにプロセスされる。(それぞれ今マクロ名として定義されている識別子はプリプロセストークンのリストで置換される。)置換の結果最終的に生成された命令は前の2形式のいずれかに合致せねばならない。(後略)
これを見る限り、C言語仕様の範疇ですね。実際、6.10.2-8に例まで載せて説明してくれています。
#if VERSION == 1
#define INCFILE "vers1.h"
#elif VERSION == 2
#define INCFILE "vers2.h" // and so on
#else
#define INCFILE "versN.h"
#endif
#include INCFILE
ちなみに、n4659(C++17のdraft)3ではどうかというと、19.2-4にほぼ同様の内容があります。
最後に
ぶっちゃけ使う機会がないですが、 久々に調べていて楽しかったです。