LoginSignup
2
1

More than 5 years have passed since last update.

self includeによるマクロの展開

Posted at

C言語への感謝の正拳突き 2日目です。

概要

最初タイトルは「再帰的なincludeにおけるマクロの展開」
としていたため、includeを繰り返してfiboの計算でもすんのか?
と思われた方もいるかもしれませんが、そういうことはModern C++のconstexprにお任せするとして、、

今回も、OSSのソースコードを読んでいて勉強になったマクロの使い方を紹介したいと思います。
全然再帰的でない、self includeと呼べるマクロの展開です。

今回見つけてきたのは、こちらのOSSです。uriparser
http://sourceforge.net/p/uriparser/git/ci/master/tree/

uriparser/include/uriparser

Uri.h            //メインのヘッダファイル
UriDefsAnsi.h    //Ansi向けのパラメータを定義するヘッダファイル
UriDefsUnicode.h //Unicode向けのパラメータを定義するヘッダファイル

Uri.hをincludeすると、その中で機種依存のパラメータを定義した後、Uri.hをself includeします。
説明するのが面倒なので先に具体例をみてみましょう。

具体例(ヘッダ)

Uri.hはincludeすると、Uri.hを2回self includeします。
1回目のself include時はAnsi用のマクロが定義されたUriDefsAnsi.hをincludeしたのち、body部分が展開されます。
2回目のself include時はUnicdoe用のマクロが定義されたUriDefsUnicode.hをincludeしたのち、body部分が展開されます。

uriparser/include/uriparser/Uri.h

/* What encodings are enabled? */
#include "UriDefsConfig.h"
#if (!defined(URI_PASS_ANSI) && !defined(URI_PASS_UNICODE))
/* Include SELF twice */
# ifdef URI_ENABLE_ANSI                                       // ANSIフラグのガード
#  define URI_PASS_ANSI 1                                     // ANSIフラグ
#  include "Uri.h"                                            // ...self include!
#  undef URI_PASS_ANSI
# endif
# ifdef URI_ENABLE_UNICODE                                   // UNICODEフラグのガード
#  define URI_PASS_UNICODE 1                                 // UNICODEフラグ
#  include "Uri.h"                                           // ...self include!
#  undef URI_PASS_UNICODE
# endif
/* Only one pass for each encoding */
#elif (defined(URI_PASS_ANSI) && !defined(URI_H_ANSI) \
  && defined(URI_ENABLE_ANSI)) || (defined(URI_PASS_UNICODE) \
  && !defined(URI_H_UNICODE) && defined(URI_ENABLE_UNICODE))
# ifdef URI_PASS_ANSI
#  define URI_H_ANSI 1
#  include "UriDefsAnsi.h"                                   // ANSI PASSの際に読み込むANSI用マクロ
# else
#  define URI_H_UNICODE 1
#  include "UriDefsUnicode.h"                                // UNICODE PASSの際に読み込むUNICODE用マクロ
# endif

素晴らしいですね。私は絶対こんなコードを書きたくないです。

以降で宣言されている構造体と関数の宣言をみてみます。

uriparser/include/uriparser/Uri.h
typedef struct URI_TYPE(TextRangeStruct) {
        const URI_CHAR * first; /**< Pointer to first character */
        const URI_CHAR * afterLast; /**< Pointer to character after the last one still in */
} URI_TYPE(TextRange); /**< @copydoc UriTextRangeStructA */

void URI_FUNC(FreeQueryList)(URI_TYPE(QueryList) * queryList);

URI_TYPEやURI_FUNCなどのマクロは、UriDefsUnicode.hとUriDefsAnsi.hでそれぞれ定義されており、
簡単に纏めると、Ansiの方は末尾にAをつけて、Unicodeの方は末尾にWをつけます。

AnsiとUnicodeのマクロ
        マクロ名     Ansi版マクロ Unicode版マクロ
#define URI_TYPE(x) Uri##x##A   Uri##x##W
#define URI_FUNC(x) uri##x##A   uri##x##W
#define URI_CHAR(x) char        wchar_t

これ、どっかのWindowsでみたことあるやつですね。。Uri.hはAnsi版とUnicode版の両方を宣言し、
Ansi版の依存部分をUriDefsAnsi.hに追い出し、Unicode版の依存部分をUriDefsUnicode.hに追い出していると。。

実際にライブラリのシンボルをみてみますと、両方生成されているのが分かりました。

liburiparser.so

    94: 0002616f    95 FUNC    GLOBAL DEFAULT   11 uriFreeQueryListA
   103: 00026a32    95 FUNC    GLOBAL DEFAULT   11 uriFreeQueryListW
   471: 0002616f    95 FUNC    GLOBAL DEFAULT   11 uriFreeQueryListA
   480: 00026a32    95 FUNC    GLOBAL DEFAULT   11 uriFreeQueryListW

具体例(ソースコード)

ソースコードのほうもみてみましょう。

UriQuery.c
/* What encodings are enabled? */
#include <uriparser/UriDefsConfig.h>
#if (!defined(URI_PASS_ANSI) && !defined(URI_PASS_UNICODE))
/* Include SELF twice */
# ifdef URI_ENABLE_ANSI
#  define URI_PASS_ANSI 1
#  include "UriQuery.c"
#  undef URI_PASS_ANSI
# endif
# ifdef URI_ENABLE_UNICODE
#  define URI_PASS_UNICODE 1
#  include "UriQuery.c"
#  undef URI_PASS_UNICODE
# endif
#else
# ifdef URI_PASS_ANSI
#  include <uriparser/UriDefsAnsi.h>
# else
#  include <uriparser/UriDefsUnicode.h>
#  include <wchar.h>
# endif


...
void URI_FUNC(FreeQueryList)(URI_TYPE(QueryList) * queryList) {
        while (queryList != NULL) {
                URI_TYPE(QueryList) * nextBackup = queryList->next;
                free((URI_CHAR *)queryList->key); /* const cast */
                free((URI_CHAR *)queryList->value); /* const cast */
                free(queryList);
                queryList = nextBackup;
        }
}

ソースコードのUriQuery.cは、ヘッダファイルUri.hと同様の方法で、UriQuery.cをself includeするようでした。
正直なところヘッダファイルにAnsi依存とUnicode依存を追い出して破綻なく記述できるのは素晴らしいと思いました。
これデバッグする際には片側だけincludeしてデバッグしてたんでしょうかね、気になります。

注意点

(1) この例は正直すっげーと思ったサンプルを例にあげているだけで、
実際にこんなテクニック使ってAnsi版とUnicode版のコード書くのか?と聞かれたら書きたくないです。

(2) マクロへ過度に頼るのは止めましょう。

(3) マクロを使った小手先の共通化や抽象化より、C++のテンプレートのほうが良い場合が多いため、ModernなC++の使用を検討しましょう。

以上

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1