C Language
プリプロセッサとマクロ
サーチ…
前書き
すべてのプリプロセッサコマンドは、ハッシュ(ポンド)記号#
始まります。 ACマクロは#define
プリプロセッサディレクティブを使って#define
されたプリプロセッサコマンドです。前処理段階では、Cプリプロセッサ(Cコンパイラの一部)は、その名前が表示されている場所にマクロの本体を置き換えるだけです。
備考
コンパイラがコード内でマクロを検出すると、単純な文字列置換が実行され、追加の操作は実行されません。このため、プリプロセッサによる変更はCプログラムのスコープを尊重しません。たとえば、マクロ定義はブロック内に限定されず、ブロックステートメントを終了する'}'
影響を受けません。
プリプロセッサは設計上、完全ではありません。プリプロセッサだけでは実行できないいくつかのタイプの計算があります。
通常、コンパイラにはコマンドラインフラグ(または設定の設定)があり、前処理フェーズの後にコンパイルを停止し、結果を検査することができます。 POSIXプラットフォームでは、このフラグは-E
。したがって、このフラグを指定してgccを実行すると、展開されたソースがstdoutに出力されます。
$ gcc -E cprog.c
多くの場合、プリプロセッサは別のプログラムとして実装されます。これはコンパイラによって呼び出され、そのプログラムの一般的な名前はcpp
です。多くのプリプロセッサは、デバッグ情報を生成するためにコンパイルの後続のフェーズで使用される、行番号に関する情報などのサポート情報を出力します。プリプロセッサがgccに基づいている場合、-Pオプションはそのような情報を抑止します。
$ cpp -P cprog.c
条件付き包含および条件付き関数の署名変更
コードのブロックを含める条件付きにするには、プリプロセッサは、(例えば、いくつかのディレクティブを持っている#if
、 #ifdef
、 #else
、 #endif
、など)。
/* Defines a conditional `printf` macro, which only prints if `DEBUG`
* has been defined
*/
#ifdef DEBUG
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif
#if
条件には通常のC関係演算子を使用できます
#if __STDC_VERSION__ >= 201112L
/* Do stuff for C11 or higher */
#elif __STDC_VERSION__ >= 199901L
/* Do stuff for C99 */
#else
/* Do stuff for pre C99 */
#endif
#if
ディレクティブはC if
文と同様に動作し、整数定数式のみを含み、キャストは含みません。 1つの追加の単項演算子、 defined( identifier )
サポートします。これは、識別子が定義されている場合は1
を返し、それ以外の場合は0
を返します。
#if defined(DEBUG) && !defined(QUIET)
#define DLOG(x) (printf(x))
#else
#define DLOG(x)
#endif
条件関数署名の変更
ほとんどの場合、アプリケーションのリリースビルドにはできるだけオーバーヘッドが少なくなると予想されます。しかし、中間ビルドのテスト中に、追加のログと発見された問題に関する情報が役立ちます。
たとえば、テストビルドを行うときに、その使用についてのログを生成するSHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd)
という関数があるとします。しかし、この関数は複数の場所で使用されており、ログを生成するときには、関数のどこから呼び出されているのかを知ることが情報の一部です。
条件付きコンパイルを使用すると、関数を宣言するインクルードファイルに次のようなことができます。これにより、標準バージョンの関数がデバッグバージョンの関数に置き換えられます。プリプロセッサは、関数SerOpPluAllRead()
への呼び出しを、関数名SerOpPluAllRead_Debug()
ます。ファイル名と関数が使用されている行番号の2つの引数があります。
条件付きコンパイルは、標準関数をデバッグバージョンでオーバーライドするかどうかを選択するために使用されます。
#if 0
// function declaration and prototype for our debug version of the function.
SHORT SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo);
// macro definition to replace function call using old name with debug function with additional arguments.
#define SerOpPluAllRead(pPif,usLock) SerOpPluAllRead_Debug(pPif,usLock,__FILE__,__LINE__)
#else
// standard function declaration that is normally used with builds.
SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd);
#endif
これにより、関数SerOpPluAllRead()
の標準バージョンを、関数のSerOpPluAllRead()
のファイルにファイル名と行番号を提供するバージョンでSerOpPluAllRead()
ことができます。
重要な考慮事項が1つあり ます。この関数を使用するファイルには、プリプロセッサが関数を変更するためにこのアプローチが使用されるヘッダファイルが含まれている必要があります。そうしないと、リンカエラーが表示されます。
関数の定義は次のようになります。このソースは、プリプロセッサがSerOpPluAllRead()
関数の名前をSerOpPluAllRead_Debug()
に変更し、2つの追加の引数、関数が呼び出されたファイルの名前へのポインタ、および行番号を含むように引数リストを変更するように要求します関数が使用されているファイルに格納されます。
#if defined(SerOpPluAllRead)
// forward declare the replacement function which we will call once we create our log.
SHORT SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd);
SHORT SerOpPluAllRead_Debug(PLUIF *pPif, USHORT usLockHnd, char *aszFilePath, int nLineNo)
{
int iLen = 0;
char xBuffer[256];
// only print the last 30 characters of the file name to shorten the logs.
iLen = strlen (aszFilePath);
if (iLen > 30) {
iLen = iLen - 30;
}
else {
iLen = 0;
}
sprintf (xBuffer, "SerOpPluAllRead_Debug(): husHandle = %d, File %s, lineno = %d", pPif->husHandle, aszFilePath + iLen, nLineNo);
IssueDebugLog(xBuffer);
// now that we have issued the log, continue with standard processing.
return SerOpPluAllRead_Special(pPif, usLockHnd);
}
// our special replacement function name for when we are generating logs.
SHORT SerOpPluAllRead_Special(PLUIF *pPif, USHORT usLockHnd)
#else
// standard, normal function name (signature) that is replaced with our debug version.
SHORT SerOpPluAllRead(PLUIF *pPif, USHORT usLockHnd)
#endif
{
if (STUB_SELF == SstReadAsMaster()) {
return OpPluAllRead(pPif, usLockHnd);
}
return OP_NOT_MASTER;
}
ソースファイルのインクルード
#include
前処理ディレクティブの最も一般的な使用方法は、次のとおりです。
#include <stdio.h>
#include "myheader.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
マクロ置換
マクロ置換の最も単純な形式は、次のようにmanifest constant
を定義することです。
#define ARRSIZE 100
int array[ARRSIZE];
これは、変数に10
を乗算して新しい値を格納する、 関数のようなマクロを定義します。
#define TIMES10(A) ((A) *= 10)
double b = 34;
int c = 23;
TIMES10(b); // good: ((b) *= 10);
TIMES10(c); // good: ((c) *= 10);
TIMES10(5); // bad: ((5) *= 10);
置換は、プログラムテキストの他の解釈の前に行われます。 TIMES10
への最初の呼び出しでは、定義からの名前A
がb
置き換えられ、拡張されたテキストが呼び出しの代わりに置かれます。このTIMES10
定義は、
#define TIMES10(A) ((A) = (A) * 10)
これはA
の置換を2回評価することができるため、望ましくない副作用を伴う可能性があるからです。
以下は、値が引数の最大値である関数型マクロを定義します。これは、関数呼び出しのオーバーヘッドなしで、引数の互換性のある型を処理し、インラインコードを生成するという利点があります。その引数の1つまたは2つを2回目(副作用を含む)評価し、数回呼び出した場合に関数より多くのコードを生成するという欠点があります。
#define max(a, b) ((a) > (b) ? (a) : (b))
int maxVal = max(11, 43); /* 43 */
int maxValExpr = max(11 + 36, 51 - 7); /* 47 */
/* Should not be done, due to expression being evaluated twice */
int j = 0, i = 0;
int sideEffect = max(++i, ++j); /* i == 4 */
このため、そのようなマクロは、通常は実動コードではその引数を複数回評価します。 C11以降、このような複数の呼び出しを避けることを可能にする_Generic
機能があります。
マクロ拡張(定義の右側)の豊富な括弧は、引数と結果の式が正しくバインドされ、マクロが呼び出されるコンテキストに適切に収まることを保証します。
エラー指示文
プリプロセッサが#error
ディレクティブを検出すると、コンパイルは停止し、インクルードされた診断メッセージが出力されます。
#define DEBUG
#ifdef DEBUG
#error "Debug Builds Not Supported"
#endif
int main(void) {
return 0;
}
可能な出力:
$ gcc error.c
error.c: error: #error "Debug Builds Not Supported"
コードセクションをブロックする#if 0
削除を検討しているコードや一時的に無効にするコードのセクションがある場合は、ブロックコメントでコメントアウトすることができます。
/* Block comment around whole function to keep it from getting used.
* What's even the purpose of this function?
int myUnusedFunction(void)
{
int i = 5;
return i;
}
*/
ただし、ブロックコメントで囲まれたソースコードのソースにブロックスタイルのコメントがある場合、既存のブロックコメントの末尾には新しいブロックコメントが無効になり、コンパイルの問題が発生する可能性があります。
/* Block comment around whole function to keep it from getting used.
* What's even the purpose of this function?
int myUnusedFunction(void)
{
int i = 5;
/* Return 5 */
return i;
}
*/
前の例では、関数の最後の2行と最後の '* /'がコンパイラによって認識されるため、エラーとともにコンパイルされます。より安全な方法は、ブロックするコードの周りに#if 0
ディレクティブを使用することです。
#if 0
/* #if 0 evaluates to false, so everything between here and the #endif are
* removed by the preprocessor. */
int myUnusedFunction(void)
{
int i = 5;
return i;
}
#endif
この利点は、コードを検索して戻ってみたいときは、すべてのコメントを検索するよりも "#if 0"を検索するほうがはるかに簡単です。
もう1つの非常に重要な利点は、 #if 0
コードをコメントアウトすることができます。これはコメントではできません。
#if 0
を使用する代わりに、 #defined
されていない名前を使用することもできますが、コードがブロックされている理由を詳しく説明しています。たとえば、無駄なデッドコードと思われる関数がある場合は、他の機能が実装されていれば、必要なコードに#if defined(POSSIBLE_DEAD_CODE)
または#if defined(FUTURE_CODE_REL_020201)
を使用することができます。その後、そのソースを削除または有効にするために戻るとき、ソースのセクションは簡単に見つけることができます。
トークンペースト
トークンペーストでは、2つのマクロ引数を結合することができます。たとえば、 front##back
はfront##back
生成しfrontback
。有名な例は、Win32の<TCHAR.H>
ヘッダーです。標準Cでは、ワイド文字列を宣言するためにL"string"
を書くことができます。ただし、Windows APIではUNICODE
#define
するだけで、ワイド文字列と狭い文字列の間の変換が可能です。文字列リテラルを実装するために、 TCHAR.H
はこれを使用します
#ifdef UNICODE
#define TEXT(x) L##x
#endif
ユーザーがTEXT("hello, world")
を記述し、UNICODEが定義されるたびに、CプリプロセッサはL
とマクロ引数を連結します。 L
は"hello, world"
と連結され"hello, world"
L"hello, world"
ます。
定義済みのマクロ
あらかじめ定義されたマクロは、プログラムを定義する必要のないCプリプロセッサによってすでに理解されているマクロです。例には
必須の事前定義マクロ
-
__FILE__
は、現在のソースファイルのファイル名(文字列リテラル)を示します。 - 現在の行番号(整数定数)の場合は
__LINE__
、 -
__DATE__
はコンパイル日時(文字列リテラル)、 - コンパイル時には
__TIME__
(文字列リテラル)。
__func__
(ISO / IEC 9899:2011§6.4.2.2)という関連する定義済み識別子もありますが、これはマクロではありません 。
識別子
__func__
は、各関数定義の開始中括弧の直後にある宣言のように、トランスレータによって暗黙的に宣言されます。static const char __func__[] = "function-name";
出現しました。ここで、 function-nameは、字句を囲む関数の名前です。
__func__
__FILE__
、 __LINE__
__func__
__FILE__
、 __LINE__
__func__
はデバッグのために特に便利です。例えば:
fprintf(stderr, "%s: %s: %d: Denominator is 0", __FILE__, __func__, __LINE__);
C99より前のコンパイラは、 __func__
サポートすることもあればサポートしないこともありますし、異なる名前を持つマクロを持つこともできます。たとえば、gccはC89モードで__FUNCTION__
を使用し__FUNCTION__
た。
以下のマクロでは実装の詳細を尋ねることができます:
-
__STDC_VERSION__
実装されたC標準のバージョン。これは、形式使用定数整数でyyyymmL
(値201112L
C11、値が199901L
C99のために、それは、C89 / C90のために定義されていませんでした) -
__STDC_HOSTED__
ホスト実装の場合は1
、そうでない場合は0
。 -
__STDC__
1
場合、実装はC標準に準拠します。
その他の事前定義マクロ(必須ではない)
ISO / IEC 9899:2011§6.10.9.2環境マクロ:
__STDC_ISO_10646__
フォームの整数定数yyyymmL
(例えば199712L)。このシンボルが定義されている場合、Unicodeの必須セットのすべての文字は、wchar_t
型のオブジェクトに格納されたときに、その文字の短い識別子と同じ値を持ちます。 Unicodeの必須セットは、ISO / IEC 10646で定義されているすべての文字と、指定された年および月のすべての改訂および技術的正誤表から構成されます。他のエンコーディングが使用されている場合、マクロは定義されず、使用される実際のエンコーディングはインプリメンテーション定義です。
__STDC_MB_MIGHT_NEQ_WC__
整数定数1は、wchar_t
のエンコーディングで整数文字定数の孤立文字として使用されるときに、基本文字セットのメンバーがその値に等しいコード値を持つ必要がないことを示すためのものです。
__STDC_UTF_16__
型の値ことを示すことを意図整数定数1、char16_t
UTF-16で符号化されます。他のエンコーディングが使用されている場合、マクロは定義されず、使用される実際のエンコーディングはインプリメンテーション定義です。
__STDC_UTF_32__
型の値ことを示すことを意図整数定数1、char32_t
UTF-32符号化されます。他のエンコーディングが使用されている場合、マクロは定義されず、使用される実際のエンコーディングはインプリメンテーション定義です。
ISO / IEC 9899:2011§6.10.8.3条件付き特徴マクロ
__STDC_ANALYZABLE__
附属書L(分析可能性)の仕様への準拠を示すための整数定数1。__STDC_IEC_559__
附属書F(IEC 60559浮動小数点演算)の仕様への準拠を示すための整数定数1。__STDC_IEC_559_COMPLEX__
附属書G(IEC 60559互換複素数算術)の仕様への準拠を示すための整数定数1。__STDC_LIB_EXT1__
整数定数201112L
。附属書K(境界チェックインタフェース)で定義された拡張のサポートを示すことを意図しています。__STDC_NO_ATOMICS__
実装が原子タイプ(_Atomic
型修飾子を含む)と<stdatomic.h>
ヘッダをサポートしていないことを示すための整数定数1。__STDC_NO_COMPLEX__
実装が複合型または<complex.h>
ヘッダーをサポートしていないことを示すための整数定数1。__STDC_NO_THREADS__
実装が<threads.h>
ヘッダをサポートしていないことを示すための整数定数1。__STDC_NO_VLA__
実装が可変長配列または可変的に変更された型をサポートしていないことを示すための整数定数1。
見出しは警備員を含む
ほとんどすべてのヘッダファイルはインクルードガードイディオムに従うべきです:
my-header-file.h
#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H
// Code body for header file
#endif
これにより、複数の場所で#include "my-header-file.h"
を#include "my-header-file.h"
、関数、変数などの重複宣言が得られなくなります。次のファイル階層を想像してみてください。
header-1.h
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
header-2.h
#include "header-1.h"
int myFunction2(MyStruct *value);
main.c
#include "header-1.h"
#include "header-2.h"
int main() {
// do something
}
このコードには深刻な問題がありますMyStruct
の詳細な内容は2回定義されていますが、これは許可されていません。 1つのヘッダーファイルに別のヘッダーファイルが含まれているため、コンパイルエラーが発生すると追跡が困難になります。代わりにヘッダーガードを使用した場合:
header-1.h
#ifndef HEADER_1_H
#define HEADER_1_H
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
#endif
header-2.h
#ifndef HEADER_2_H
#define HEADER_2_H
#include "header-1.h"
int myFunction2(MyStruct *value);
#endif
main.c
#include "header-1.h"
#include "header-2.h"
int main() {
// do something
}
これは次に展開されます:
#ifndef HEADER_1_H
#define HEADER_1_H
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
#endif
#ifndef HEADER_2_H
#define HEADER_2_H
#ifndef HEADER_1_H // Safe, since HEADER_1_H was #define'd before.
#define HEADER_1_H
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
#endif
int myFunction2(MyStruct *value);
#endif
int main() {
// do something
}
コンパイラがheader-1.hの 2番目のインクルードに達すると、 HEADER_1_H
は以前のインクルードによって既に定義されていました。エルゴ、それは次のようなものです。
#define HEADER_1_H
typedef struct {
…
} MyStruct;
int myFunction(MyStruct *value);
#define HEADER_2_H
int myFunction2(MyStruct *value);
int main() {
// do something
}
したがって、コンパイルエラーはありません。
注:ヘッダーガードの命名規則には、複数の異なる規則があります。 HEADER_2_H_
という名前の人もいれHEADER_2_H_
、 MY_PROJECT_HEADER_2_H
ようなプロジェクト名が含まれる人もいます。重要なのは、プロジェクトの各ファイルに一意のヘッダーガードがあるように、それに従うようにすることです。
構造体の詳細がヘッダーに含まれていない場合、宣言された型は不完全または不透明な型になります。そのような型は、関数のユーザーから実装の詳細を隠すのに便利です。多くの目的のために、標準CライブラリのFILE
型は不透明型と見なすことができます(標準I / O関数のマクロ実装が構造体の内部を利用できるように、通常は不透明ではありません)。その場合、 header-1.h
はheader-1.h
が含まれます。
#ifndef HEADER_1_H
#define HEADER_1_H
typedef struct MyStruct MyStruct;
int myFunction(MyStruct *value);
#endif
構造体にはタグ名(ここではMyStruct
- これはタグ名前空間にあり、typedef名MyStruct
通常の識別子名前空間とは別MyStruct
)が必要であり、 { … }
が省略されている必要があります。これは、 "構造体型のstruct MyStruct
があり、それにMyStruct
別名がありMyStruct
"と表示されます。
実装ファイルでは、構造体の詳細を定義して、型を完全にすることができます。
struct MyStruct {
…
};
C11を使用している場合、 typedef struct MyStruct MyStruct;
繰り返すことができtypedef struct MyStruct MyStruct;
コンパイルエラーは発生しませんが、以前のバージョンのCでは不平を言います。したがって、この例では、C11をサポートするコンパイラでコードがコンパイルされただけであっても、インクルードガードイディオムを使用することが最良です。
多くのコンパイラは同じ結果を持つ#pragma once
指令をサポートしてい#pragma once
。
my-header-file.h
#pragma once
// Code for header file
ただし、 #pragma once
はC標準の一部ではないため、コードを使用すると移植性が低下します。
いくつかのヘッダーはインクルードガードイディオムを使用しません。 1つの具体的な例は、標準の<assert.h>
ヘッダーです。 1つの翻訳単位に複数回含めることができます。その結果は、ヘッダがインクルードされるたびにマクロNDEBUG
が定義されているかどうかによって異なります。時には類似の要件があるかもしれません。そのような場合は、ごくわずかなものになります。通常、ヘッダーはインクルードガードイディオムで保護されている必要があります。
FOREACHの実装
また、コードを読みやすく書くためにマクロを使用することもできます。例えば、単一リンクや二重リンクのリスト、キューなどのいくつかのデータ構造に対して、Cでforeach
構造を実装するためのマクロを実装できます。
ここに小さな例があります。
#include <stdio.h>
#include <stdlib.h>
struct LinkedListNode
{
int data;
struct LinkedListNode *next;
};
#define FOREACH_LIST(node, list) \
for (node=list; node; node=node->next)
/* Usage */
int main(void)
{
struct LinkedListNode *list, **plist = &list, *node;
int i;
for (i=0; i<10; i++)
{
*plist = malloc(sizeof(struct LinkedListNode));
(*plist)->data = i;
(*plist)->next = NULL;
plist = &(*plist)->next;
}
/* printing the elements here */
FOREACH_LIST(node, list)
{
printf("%d\n", node->data);
}
}
このようなデータ構造の標準インタフェースを作成し、 FOREACH
汎用実装を次のように書くことができます。
#include <stdio.h>
#include <stdlib.h>
typedef struct CollectionItem_
{
int data;
struct CollectionItem_ *next;
} CollectionItem;
typedef struct Collection_
{
/* interface functions */
void* (*first)(void *coll);
void* (*last) (void *coll);
void* (*next) (void *coll, CollectionItem *currItem);
CollectionItem *collectionHead;
/* Other fields */
} Collection;
/* must implement */
void *first(void *coll)
{
return ((Collection*)coll)->collectionHead;
}
/* must implement */
void *last(void *coll)
{
return NULL;
}
/* must implement */
void *next(void *coll, CollectionItem *curr)
{
return curr->next;
}
CollectionItem *new_CollectionItem(int data)
{
CollectionItem *item = malloc(sizeof(CollectionItem));
item->data = data;
item->next = NULL;
return item;
}
void Add_Collection(Collection *coll, int data)
{
CollectionItem **item = &coll->collectionHead;
while(*item)
item = &(*item)->next;
(*item) = new_CollectionItem(data);
}
Collection *new_Collection()
{
Collection *nc = malloc(sizeof(Collection));
nc->first = first;
nc->last = last;
nc->next = next;
return nc;
}
/* generic implementation */
#define FOREACH(node, collection) \
for (node = (collection)->first(collection); \
node != (collection)->last(collection); \
node = (collection)->next(collection, node))
int main(void)
{
Collection *coll = new_Collection();
CollectionItem *node;
int i;
for(i=0; i<10; i++)
{
Add_Collection(coll, i);
}
/* printing the elements here */
FOREACH(node, coll)
{
printf("%d\n", node->data);
}
}
この汎用実装を使用するには、データ構造にこれらの関数を実装するだけです。
1. void* (*first)(void *coll);
2. void* (*last) (void *coll);
3. void* (*next) (void *coll, CollectionItem *currItem);
C ++でコンパイルされたC ++コードでCの外部関数を使用するための__cplusplus - name mangling
言語の違いにより、コンパイラがCコンパイラかC ++コンパイラかによって、インクルードファイルがプリプロセッサと異なる出力を生成することがあります。
たとえば、関数やその他の外部関数は、Cソースファイルで定義されていますが、C ++ソースファイルで使用されています。関数の引数の型に基づいて一意の関数名を生成するためにC ++は名前マングリング(または名前修飾)を使用するため、C ++ソースファイルで使用されるC関数宣言はリンクエラーを引き起こします。 C ++コンパイラーは、C ++の名前付け規則を使用して、コンパイラー出力の指定された外部名を変更します。その結果、C ++コンパイラ出力がCコンパイラ出力にリンクされているときに外部からのリンクエラーが検出されません。
Cコンパイラはネームマングリングを行いませんが、C ++コンパイラによって生成されたすべての外部ラベル(関数名または変数名)に対してC ++コンパイラが行うので、コンパイラ検出を可能にするために事前定義されたプリプロセッサマクロ__cplusplus
が導入されました。
CとC ++間の外部名の互換性のないコンパイラ出力のこの問題を回避するために、マクロ__cplusplus
はC ++プリプロセッサで定義され、Cプリプロセッサで定義されていません。このマクロ名は、ソースコードまたはインクルードファイルがC ++またはCとしてコンパイルされているかどうかを判断するために、条件付きプリプロセッサ#ifdef
ディレクティブまたは#if
とdefined()
演算子で使用できます。
#ifdef __cplusplus
printf("C++\n");
#else
printf("C\n");
#endif
または、あなたが使うことができる
#if defined(__cplusplus)
printf("C++\n");
#else
printf("C\n");
#endif
C ++ソースファイルで使用されているCコンパイラでコンパイルされたCソースファイルから関数の正しい関数名を指定するには、 __cplusplus
定義定数を調べてextern "C" { /* ... */ };
ヘッダーファイルがC ++ソースファイルに含まれているときにC外部を宣言するために使用されます。しかし、Cコンパイラでコンパイルすると、 extern "C" { */ ... */ };
使用されていません。この条件付きコンパイルが必要なのは、 extern "C" { /* ... */ };
C ++では有効ですが、Cでは有効ではありません。
#ifdef __cplusplus
// if we are being compiled with a C++ compiler then declare the
// following functions as C functions to prevent name mangling.
extern "C" {
#endif
// exported C function list.
int foo (void);
#ifdef __cplusplus
// if this is a C++ compiler, we need to close off the extern declaration.
};
#endif
関数のようなマクロ
関数のようなマクロはinline
関数に似ていますが、一時的なデバッグログなどの場合に便利です。
#ifdef DEBUG
# define LOGFILENAME "/tmp/logfile.log"
# define LOG(str) do { \
FILE *fp = fopen(LOGFILENAME, "a"); \
if (fp) { \
fprintf(fp, "%s:%d %s\n", __FILE__, __LINE__, \
/* don't print null pointer */ \
str ?str :"<null>"); \
fclose(fp); \
} \
else { \
perror("Opening '" LOGFILENAME "' failed"); \
} \
} while (0)
#else
/* Make it a NOOP if DEBUG is not defined. */
# define LOG(LINE) (void)0
#endif
#include <stdio.h>
int main(int argc, char* argv[])
{
if (argc > 1)
LOG("There are command line arguments");
else
LOG("No command line arguments");
return 0;
}
両方の場合( DEBUG
場合とそうでない場合)、呼び出しはvoid
戻り型の関数と同じように動作しvoid
。これにより、 if/else
条件が期待どおりに解釈されるようになります。
DEBUG
場合、これはdo { ... } while(0)
構文do { ... } while(0)
使って実装されます。それ以外の場合、 (void)0
は副作用のないステートメントであり、無視されます。
後者の代替案は
#define LOG(LINE) do { /* empty */ } while (0)
それはすべての場合において最初のものと構文的に等価である。
GCCを使用する場合は、非標準のGNU拡張命令文を使用して結果を返す関数型マクロを実装することもできます。例えば:
#include <stdio.h>
#define POW(X, Y) \
({ \
int i, r = 1; \
for (i = 0; i < Y; ++i) \
r *= X; \
r; \ // returned value is result of last operation
})
int main(void)
{
int result;
result = POW(2, 3);
printf("Result: %d\n", result);
}
可変引数マクロ
可変引数を持つマクロ:
コードをデバッグするためのプリントマクロを作成したいとします。このマクロを例に挙げてみましょう:
#define debug_print(msg) printf("%s:%d %s", __FILE__, __LINE__, msg)
使用法のいくつかの例:
関数somefunc()
は、失敗した場合は-1を返し、成功した場合は0を返し、コード内のさまざまな場所から呼び出されます。
int retVal = somefunc();
if(retVal == -1)
{
debug_printf("somefunc() has failed");
}
/* some other code */
retVal = somefunc();
if(retVal == -1)
{
debug_printf("somefunc() has failed");
}
somefunc()
実装が変更され、異なる可能性のあるエラータイプに一致する異なる値が返されるとどうなりますか?それでも、デバッグマクロを使用してエラー値を出力する必要があります。
debug_printf(retVal); /* this would obviously fail */
debug_printf("%d",retVal); /* this would also fail */
この問題を解決するために、 __VA_ARGS__
マクロが導入されました。このマクロは複数のパラメータXマクロを許可します:
例:
#define debug_print(msg, ...) printf(msg, __VA_ARGS__) \
printf("\nError occurred in file:line (%s:%d)\n", __FILE__, __LINE)
使用法:
int retVal = somefunc();
debug_print("retVal of somefunc() is-> %d", retVal);
このマクロでは、複数のパラメータを渡して印刷することができますが、今ではパラメータの送信を禁止しています。
debug_print("Hey");
これは、マクロが少なくとももう1つの引数を必要とするため、 debug_print()
はdebug_print()
マクロ内のカンマの欠如を無視しないため、構文エラーが発生します。また、 debug_print("Hey",);
マクロに渡された引数を空にしておくことができないので、構文エラーが発生します。
これを解決するために、 ##__VA_ARGS__
マクロが導入されました。このマクロは可変引数が存在しない場合、コードからプリプロセッサによってコンマが削除されることを示しています。
例:
#define debug_print(msg, ...) printf(msg, ##__VA_ARGS__) \
printf("\nError occured in file:line (%s:%d)\n", __FILE__, __LINE)
使用法:
debug_print("Ret val of somefunc()?");
debug_print("%d",somefunc());