C言語/プリプロセッシングディレクティブ

プリプロセッサの概要

編集

プリプロセッサの役割と機能

編集

C言語のプリプロセッサは、コンパイルの前処理を行うツールです。ソースコードに対して一連の変換を実行し、その結果をコンパイラに渡します。プリプロセッサは、コードの条件付きコンパイル、マクロ定義と置換、ファイルのインクルードなどの機能を提供します。

プリプロセッシングディレクティブとは

編集

プリプロセッシングディレクティブは、#で始まる行として記述され、プリプロセッサに特定の操作を指示する指令です。これには、マクロの定義、ファイルのインクルード、条件付きコンパイルなどが含まれます。

基本構文

編集

プリプロセッシングディレクティブは、一連のプリプロセッシングトークンとして表現されます。

プリプロセッシングファイルの構文(EBNF)
preprocessing-file = [group]

group = group-part
      | group, group-part

group-part = if-section
           | control-line 
           | text-line
           | "#", non-directive

if-section = if-group, [elif-groups], [else-group], endif-line

if-group = "#", "if", constant-expression, new-line, [group]
         | "#", "ifdef", identifier, new-line, [group]  
         | "#", "ifndef", identifier, new-line, [group]

elif-groups = elif-group
            | elif-groups, elif-group             

elif-group = "#", "elif", constant-expression, new-line, [group]
           | "#", "elifdef", identifier, new-line, [group]
           | "#", "elifndef", identifier, new-line, [group]

else-group = "#", "else", new-line, [group]

endif-line = "#", "endif", new-line

control-line = "#", "include", pp-tokens, new-line
             | "#", "embed", pp-tokens, new-line
             | "#", "define", identifier, replacement-list, new-line
             | "#", "define", identifier, lparen, [identifier-list], ")", replacement-list, new-line
             | "#", "define", identifier, lparen, "...", ")", replacement-list, new-line
             | "#", "define", identifier, lparen, identifier-list, ",", "...", ")", replacement-list, new-line
             | "#", "undef", identifier, new-line
             | "#", "line", pp-tokens, new-line
             | "#", "error", [pp-tokens], new-line
             | "#", "warning", [pp-tokens], new-line
             | "#", "pragma", [pp-tokens], new-line
             | "#", new-line

text-line = [pp-tokens], new-line

non-directive = pp-tokens, new-line

lparen = "(" ;; 直前に空白がない"("

replacement-list = [pp-tokens]

pp-tokens = preprocessing-token
          | pp-tokens, preprocessing-token
          
new-line = /* the new-line character */

identifier-list = identifier
                | identifier-list, ",", identifier

pp-parameter = pp-parameter-name, [pp-parameter-clause]

pp-parameter-name = pp-standard-parameter
                  | pp-prefixed-parameter

pp-standard-parameter = identifier

pp-prefixed-parameter = identifier, "::", identifier

pp-parameter-clause = "(", [pp-balanced-token-sequence], ")"

pp-balanced-token-sequence = pp-balanced-token
                            | pp-balanced-token-sequence, pp-balanced-token
                            
pp-balanced-token = "(", [pp-balanced-token-sequence], ")"
                  | "[", [pp-balanced-token-sequence], "]"
                  | "{", [pp-balanced-token-sequence], "}"
                  | /* any pp-token other than a parenthesis, bracket or brace */

embed-parameter-sequence = pp-parameter
                         | embed-parameter-sequence, pp-parameter

プリプロセッシングディレクティブの一般的な形式

編集

プリプロセッシングディレクティブは、次のように記述されます:

#directive [optional_tokens] new_line
  • #:ディレクティブの開始
  • directive:ディレクティブ名
  • optional_tokens:ディレクティブに必要な追加情報
  • new_line:改行文字

条件付きディレクティブ

編集

条件付きディレクティブは、コード内で特定の条件が満たされた場合に、特定のコードブロックを含めるために使用されます。#if#elif#ifdef#ifndef#elifdefC23#elifndefC23 のディレクティブが条件付きディレクティブを制御します。

構文

編集

条件付きディレクティブの制御には、次の要素が使われます:

defined マクロ式
defined identifier
defined ( identifier )
h-preprocessing-token
> を除くあらゆるプリプロセッシングトークン
h-pp-tokens
h-preprocessing-token
h-pp-tokens h-preprocessing-token
header-name-tokens
文字列リテラル
< h-pp-tokens >
has-include-expression
__has_include ( header-name )
__has_include ( header-name-tokens )
has-embed-expression
__has_embed ( header-name [ embed-parameter-sequence ] )
__has_embed ( header-name-tokens [ pp-balanced-token-sequence ] )
has-c-attribute-express
__has_c_attribute ( pp-tokens )

制約

編集

条件付きディレクティブを制御する式には、次の制約があります:

  • 条件式は整数定数式でなければなりませんが、識別子は特定の形式で解釈されます。
  • defined マクロ式 は、識別子が現在マクロ名として定義されているかどうかを評価します。
  • has_include 式および has_embed 式は、指定されたファイルまたはリソースが見つかるかどうかを評価します。
  • has_c_attribute 式は、実装が指定された属性をサポートしているかどうかを評価します。

意味論

編集

条件付きディレクティブの意味論は、条件式が真の場合に特定のコードブロックを処理することです。各ディレクティブの条件は順番にチェックされ、最初に真の条件に一致したグループが処理されます。条件が真でない場合は、次のディレクティブが評価されます。条件が一致しない場合は、#else ディレクティブがあればそのグループが処理されます。条件が一致しない場合は、そのブロック全体がスキップされます。

条件付きディレクティブは、特定のコンパイラの機能や特定の環境のサポートを確認し、適切なコードパスを選択するために広く使用されます。

ソースファイルのインクルード

編集

ファイルのインクルードは、Cプリプロセッサの重要な機能の1つです。#include ディレクティブを使用して、他のファイルの内容を現在のソースファイルに挿入することができます。これにより、同じコードを複数のファイルで共有したり、外部のライブラリを使用したりすることが可能になります。

制約

編集

ソースファイルのインクルードに関する制約は比較的シンプルです。#include ディレクティブは、処理できるヘッダーまたはソースファイルを指定する必要があります。つまり、指定されたファイルが実装によって処理可能でなければなりません。

意味論

編集

ソースファイルのインクルードは、2つの異なる形式を取ることができます。

  1. < h-char-sequence > で囲まれた形式は、システムのヘッダーをインクルードするために使用されます。この形式では、指定されたヘッダーをシステムが定義する場所から検索し、その内容を現在のソースファイルに挿入します。
  2. " q-char-sequence " で囲まれた形式は、プロジェクト内のカスタムヘッダーや他のソースファイルをインクルードするために使用されます。この形式では、指定されたファイルを実装が定義する場所から検索し、その内容を挿入します。この検索が失敗すると、システムヘッダーの検索形式で再度検索を試みます。

以下は、#include ディレクティブの一般的な使用例です。

#include <stdio.h>      // システムヘッダーのインクルード
#include "myprog.h"     // カスタムヘッダーのインクルード

#include ディレクティブの特殊な使い方として、マクロの置換を利用することもあります。これにより、バージョン別のヘッダーを選択的にインクルードすることができます。

#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

これにより、条件に応じて異なるヘッダーが選択され、挿入されます。

バイナリリソースの埋め込み

編集

C言語では、ソースコードにバイナリリソース(例えば、画像やデータファイル)を直接埋め込むことができる機能が提供されています。これを実現するのが #embed プリプロセッシングディレクティブです。このディレクティブを使用することで、バイナリリソースをソースコード内で簡単に利用できるようになります。

#embed プリプロセッシングディレクティブの使用方法

編集

基本形式

編集

#embed ディレクティブは次のように記述します:

#embed "resource_path"

または

#embed <resource_path>

ここで、resource_path は埋め込みたいリソースのファイルパスを示します。" または < > で囲まれたファイルパスを指定します。

例1:基本的な使用例
以下は、画像ファイル black_sheep.ico をプログラムに埋め込む例です:
#include <stddef.h>

void display_image(const unsigned char*, size_t);

int main(int argc, char* argv[]) {
    static const unsigned char image[] = {
#embed "black_sheep.ico"
    };
    display_image(image, sizeof(image));
    return 0;
}

この例では、black_sheep.ico ファイルの内容が image 配列に埋め込まれ、display_image 関数に渡されています。

埋め込みパラメータ

編集

#embed ディレクティブには、オプションの埋め込みパラメータを指定することができます。埋め込みパラメータは、埋め込まれるデータの制限や修正を行うために使用されます。

例2:limit パラメータの使用
limit パラメータを使用して、埋め込むデータのバイト数を制限することができます:
int main() {
    unsigned char data[] = {
#embed "data.bin" limit(100)
    };
    // data.bin の最初の 100 バイトが data 配列に埋め込まれる
    return 0;
}

追加パラメータ

編集

#embed ディレクティブは、以下の追加パラメータをサポートしています:

  • prefix: 埋め込みデータの前に指定されたバイトを追加します。
  • suffix: 埋め込みデータの後に指定されたバイトを追加します。
  • if_empty: リソースが空の場合に使用するバイト列を指定します。
例3:prefix と suffix パラメータの使用
以下は、埋め込みデータの前後にバイトを追加する例です:
unsigned char data[] = {
#embed "data.bin" prefix(0xFE, 0xED) suffix(0xDE, 0xAD)
};
// data 配列には、0xFE, 0xED, data.bin の内容, 0xDE, 0xAD が含まれる

意味論

編集

#embed ディレクティブは、指定されたリソースのバイナリデータを整数定数のシーケンスとして展開します。このシーケンスは、リソースのビットストリームの値を保持するように変換されます。

リソースの検索

編集

リソースの検索方法は実装定義です。一般的には、ソースファイルのインクルードパスに似たメカニズムが使用されます。実装によっては、リソースが見つからない場合にエラーが発生することがあります。

まとめ

編集

#embed プリプロセッシングディレクティブを使用することで、バイナリリソースをソースコードに直接埋め込むことが可能です。これにより、リソースの管理が容易になり、リソースファイルの一貫性が保たれます。ディレクティブのパラメータを活用することで、埋め込まれるデータの制御や修正も柔軟に行うことができます。

マクロの定義と置換

編集

#defineディレクティブの基本

編集

#defineを使用してマクロを定義します。

#define PI 3.14
#define MAX(a, b) ((a) > (b) ? (a) : (b))

マクロのパラメータと可変長引数

編集

マクロには引数を取るものと取らないものがあります。可変長引数もサポートされています。

#define PRINT(fmt, ...) printf(fmt, __VA_ARGS__)

マクロの置換リストと使用方法

編集

マクロは、定義された名前を使用すると、その定義に置換されます。

#define SQUARE(x) ((x) * (x))

int main() {
    int a = SQUARE(5);  // a = 25
}

#undefディレクティブ

編集

#undefを使用して、既存のマクロ定義を無効にします。

#define TEMP 100
#undef TEMP

行制御

編集

#lineディレクティブ

編集

#lineディレクティブを使用して、行番号とファイル名を制御します。これは、デバッグ情報を制御するのに役立ちます。

#line 100 "newfile.c"

これにより、次の行の行番号は100として扱われ、ファイル名は"newfile.c"として扱われます。

診断ディレクティブ

編集

#errorディレクティブ

編集

#errorディレクティブを使用して、コンパイル時にエラーメッセージを生成します。

#ifndef REQUIRED_MACRO
#error "REQUIRED_MACRO is not defined"
#endif

#warningディレクティブ

編集

#warningディレクティブを使用して、コンパイル時に警告メッセージを生成します。

#warning "This is a warning message"

プラグマディレクティブ

編集

#pragmaディレクティブの使用方法

編集

#pragmaディレクティブを使用して、特定のコンパイラ機能を有効または無効にします。

#pragma pack(push, 1)
// 構造体定義
#pragma pack(pop)

各種プラグマの例

編集
  • #pragma once:多重インクルードを防止します。
  • #pragma pack:メモリ配置の制御を行います。

ヌルディレクティブ

編集

#のみの行の扱い

編集

ヌルディレクティブは、#の後に何も続かない行です。これは何も実行せず、通常は無視されます。

#

事前定義されたマクロ名

編集

標準のプリプロセッサマクロ

編集

C標準ライブラリにはいくつかの事前定義されたマクロがあります。

__FILE__  // 現在のソースファイル名
__LINE__  // 現在の行番号
__DATE__  // 現在のソースファイルのコンパイル日
__TIME__  // 現在のソースファイルのコンパイル時刻

カスタムマクロとの違い

編集

標準マクロは、コンパイラによって自動的に定義されます。カスタムマクロはユーザーが手動で定義するものです。

プラグマオペレーター

編集

プラグマオペレーター_Pragmaの使用方法

編集

プラグマ

オペレーターは、#pragmaディレクティブをマクロ内で使用するために提供されます。

#define DO_PRAGMA(x) _Pragma(#x)
DO_PRAGMA(pack(push, 1))

まとめと実践問題

編集

章全体のまとめ

編集

この章では、プリプロセッシングディレクティブの基本的な使い方、条件付きコンパイル、ファイルインクルード、マクロの定義と置換、診断ディレクティブ、プラグマディレクティブ、ヌルディレクティブ、事前定義されたマクロ名、プラグマオペレーターについて学びました。

章末問題

編集
問題1
マクロを使用して円の面積を計算するプログラムを書いてください。
問題2
インクルードガードを使用してヘッダーを保護してください。
問題3
#errorディレクティブを使用して、特定のマクロが定義されていない場合にエラーメッセージを表示するコードを書いてください。