C99, C++11の可変引数マクロでは、
#define AAA(a, ...)
のように省略記号の前にパラメータがあるマクロを定義したとき、
AAA(hoge, fuga);
という呼び出しはマクロのパラメータ数より、引数の数が多いのでwell-formedつまり正しいけれども、
AAA(hoge);
という呼び出しはマクロのパラメータ数と引数の数が同じなのでill-formed つまり不正になるようです。
なので、C99, C++11に準拠して書こうと思うと、可変引数なしのマクロと、可変引数ありのマクロを準備して、呼び分けなければなりません。非常に面倒くさいです…。
#define AAA(a)
#define BBB(a, ...)
AAA(hoge);
//AAA(hoge, fuga); //ill-formed
//BBB(hoge); //ill-formed
BBB(hoge, fuga);
具体的に規格のどこに違反するのかよくわかりませんが、下記の文章ですかね?私の翻訳はあやしいので、原文も併せて載せます。
N3337 Working Draft, Standard for Programming Language C++
16.3 Macro replacement [cpp.replace]
...
12. If there is a ... in the identifier-list in the macro definition, then the trailing arguments, including any separating comma preprocessing tokens, are merged to form a single item: the variable arguments. The number of arguments so combined is such that, following merger, the number of arguments is one more than the number of parameters in the macro definition (excluding the ...).
(参考訳)
もし省略記号(...)がマクロ定義内のidentifier-listに存在する場合、後続の引数(カンマで区切られた全てのpreprocessing-token)は一つの項目としてまとめられる。これが可変引数である。
このようにまとめられた後の引数の数は、マクロ定義内の省略記号(...)を除いたパラメータ数より一つ多くなる。
何言ってんだお前?と思わなくもない文章ですが…、定義が #define AAA(a, b, ...) のマクロをAAA(i1, i2, i3, i4) と4つの引数でマクロを呼ぶと、省略記号の部分がまとめられi1, i2, (残り全部) の3つになるから、マクロ定義内のパラメータa, bの数である2つに比べて1個多くなる、くらいの意味でしょうか。
しかし、可変引数を空にしてマクロを呼ぶと、引数の数が同じになってしまって項目12の既定に反するので、文法違反とせざるを得ない、という解釈なのでしょう。
あとidentifier-listは16 Preprocessing directives [cpp] に、preprocessing-tokenは2.5 Preprocessing Tokens [lex.pptoken] に規定されている構文要素を指していると思われるので、訳していません。
ただし、GCCはAAA(hoge); と書いても何も警告してきません。これは「可変引数が空っぽのマクロ呼び出しを許す」つまりAAA(hoge); という呼び出しを許すGCCの拡張構文らしいです。
ちなみにgcc -pedantic-errorsとすると拡張構文の使用箇所をエラー扱いするようになりますので、
error: ISO C99 requires rest arguments to be used AAA("hoge");
と怒ってきます。
可変引数マクロを使ってオレオレprintfマクロを作れますが、その時に必ずぶつかる問題があって、
#include <cstdio>
#define DEBUG_LOG(fmt, ...) std::printf(fmt, __VA_ARGS__)
int main(int argc, char *argv[])
{
DEBUG_LOG("Hello World.\n");
DEBUG_LOG("Hello World %d.\n", 2);
return 0;
}
せっかくGNU拡張構文を使って、最初の呼び出し(引数が1つだけ)を可能にしたのに、いざコンパイルしてみると、引数が無いよ?と怒られてしまう問題です。
$ g++ -Wall --std=c++0x variadic_macros.cpp variadic_macros.cpp: In function ‘int main(int, char**)’: variadic_macros.cpp:3:57: error: expected primary-expression before ‘)’ token #define DEBUG_LOG(fmt, ...) std::printf(fmt, __VA_ARGS__) ^ variadic_macros.cpp:7:5: note: in expansion of macro ‘DEBUG_LOG’ DEBUG_LOG("Hello World.
");
もちろんこの問題はGNUの人たちはわかっていて、解決策もあります。そうですGNU拡張構文です。先ほどのマクロ定義を下記のように変えます。
#include <cstdio>
#define DEBUG_LOG(fmt, ...) std::printf(fmt, ##__VA_ARGS__)
int main(int argc, char *argv[])
{
DEBUG_LOG("Hello World.\n");
DEBUG_LOG("Hello World %d.\n", 2);
return 0;
}
違いは、__VA_ARGS__ の前に ## が付いていることです。## はトークン連結演算子といって、プリプロセッサ時点で、## の前後を結合して一つのトークンにする演算子です。
$ g++ -Wall --std=c++0x variadic_macros.cpp $ ./a.out Hello World. Hello World 2.
この拡張構文の仕様を正確に説明できませんが、カンマと __VA_ARGS__ をトークン連結する際に、可変引数が空(つまり __VA_ARGS__ が空)ならカンマを消し去る、という動作をしているようです。
これら2つのGNU拡張構文「可変引数が空っぽのマクロ呼び出しを許す」と「カンマと __VA_ARGS__ をトークン連結する」のおかげで、printf風のマクロが自然に書けるようになります。
GNU独自の拡張構文とは言いますが、実はclangやVisual Studioでも使えます。そのためPC向けのコードであれば移植性をあまり気にせず使える機能となっています。
皆さん同じ問題に困っていたのでしょう。もうC, C++ 規格側を変えた方が良い気がするんですけど…、何か互換性の問題があるのでしょうか。
コメントでも指摘いただきましたが、上記の例だと具体的に何が問題か伝わらないので追記します。
下記のように書けば、引数が1個でも文法的にOKで、先頭に固定されたヘッダ名を足すこともできます。
#include <cstdio>
#define DEBUG_LOG(...) std::printf("debug: " __VA_ARGS__)
int main(int argc, char *argv[])
{
DEBUG_LOG("Hello World.\n"); //well-formed
DEBUG_LOG("Hello World %d.\n", 2); //well-formed
return 0;
}
$ g++ -Wall --std=c++0x variadic_macros.cpp -pedantic-errors $ ./a.out debug: Hello World. debug: Hello World 2.
ところがもう少し凝ったこと、例えばヘッダの文字列を場合によってError: に変えたい、関数名を出したいなど、何かしら引数を渡さなければならない場合は、上記の定義では実現できません。
#include <cstdio>
//やりたいこと:
// この呼び出しを…
// DEBUG_LOG("Hello World.\n");
// このように展開したい
// printf("%s: " "Hello World.\n", __func__);
//このマクロ定義なら記述可能
#define DEBUG_LOG(fmt, ...) printf("%s: " fmt, __func__, ##__VAR_ARGS__)
//このマクロ定義では記述不可能、__func__ を書ける場所が無いため
//#define DEBUG_LOG(...) printf("%s: " __VA_ARGS__ ...?
int main(int argc, char *argv[])
{
DEBUG_LOG("Hello World.\n");
DEBUG_LOG("Hello World %d.\n", 2);
return 0;
}
$ g++ -Wall --std=c++0x variadic_macros.cpp $ ./a.out main: Hello World. main: Hello World 2.
GNU拡張構文のありがたみが身に沁みますね。
自作ARMエミュレータememu(リンク)の仮想ターミナルが白黒で寂しいので、色が出るようにしてみました。ECMA-048のSGR (Select Graphic Rendition) の一部に対応しています。
ところで、今回作るまで存在すら知らなかった機能に、ネガ・ポジ機能(※)があります。
規格見たときは「何これ?誰が使うんだ?」って思ってたんですが、実はtopコマンドのPID PPID USER PR ... という見出し(背景が白い部分)部分で、しれっと使われています。
いつも目にしている画面なのに、案外知らないものです…。
(※)普通の「ネガ」は色相反転を指しますが、ememuでは前景色と背景色の入れ替えで実現しました。他のターミナルも入れ替えで対応しているようです。
メモ: 技術系の話はFacebookから転記しておくことにした。
GNU autotoolsの話を書いていて思い出したのですが、autotools + libtoolで動的ライブラリをビルドしたとき、生成されたlibxxxx.soを見失ったのは、きっと俺だけじゃないはず。
正解はMakefile.amの有るディレクトリに .libsという隠しディレクトリが出来て、その中にlibxxxx.soが置かれます(※)。
おそらくMakefile.amと同じディレクトリに置くと、不都合なことがあったからだと思われますが、何がダメだったのかサッパリ想像付きません…。
(※)生成物を .libsに置くのはGNU libtoolの仕様らしいので、正確にはGNU autotoolsは関係ありません。
調べてみたら、意味不明な .libsディレクトリの件、libtoolのマニュアル(マニュアルへのリンク)に、はっきりと書いてありました。
Note how libtool creates extra files in the .libs subdirectory, rather than the current directory. This feature is to make it easier to clean up the build directory, and to help ensure that other programs fail horribly if you accidentally forget to use libtool when you should.
だそうで、
というお気遣いのようです。
でも今はバージョン管理システムがあるから、クリーンナップのお気遣いは不要ですし、libtoolは大抵autotoolsと組み合わせるので、libtoolのせいで失敗したかどうか容易にわかりません。
優しい気遣いのはずが、今となっては壮絶な空振りで、見ていて悲しくなります…。
メモ: 技術系の話はFacebookから転記しておくことにした。
GNU autotoolsは簡単なんですが、使い始めるまではかなり難儀した記憶があります。例えば、下記のようにlibtest_new.soという名前の「動的ライブラリ」を作成するプロジェクトを作ったとします。
. |-- Makefile.am |-- configure.ac `-- test |-- Makefile.am |-- common | |-- Makefile.am | `-- utils.c `-- test.c ---- configure.ac ---- AC_PREREQ(2.61) AC_INIT([automake_test], [0.1]) AC_ARG_PROGRAM() AC_CONFIG_SRCDIR([test/test.c]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_AUX_DIR([conf]) ★ここで指定したディレクトリに注目★ AM_INIT_AUTOMAKE([foreign]) LT_INIT() ★これも注目★ AC_CONFIG_FILES([Makefile test/Makefile test/common/Makefile]) AC_OUTPUT() ---- test/Makefile.am ---- SUBDIRS = common test_newdir = $(libdir) test_new_LTLIBRARIES = libtest_new.la libtest_new_la_SOURCES = test.c libtest_new_la_LIBADD = common/libcommon.la ---- test/common/Makefile.am ---- noinst_LTLIBRARIES = libcommon.la libcommon_la_SOURCES = utils.c
基本的にGNU autotoolsを使う時はautoreconfというコマンドを実行すれば、勝手に全ての関連ファイルが更新されます。
ただ残念なことに、初回だけはそうもいきません。
$ autoreconf configure.ac:16: error: required file 'conf/compile' not found configure.ac:16: 'automake --add-missing' can install 'compile' configure.ac:16: error: required file 'conf/config.guess' not found configure.ac:16: 'automake --add-missing' can install 'config.guess' configure.ac:16: error: required file 'conf/config.sub' not found configure.ac:16: 'automake --add-missing' can install 'config.sub' configure.ac:15: error: required file 'conf/install-sh' not found configure.ac:15: 'automake --add-missing' can install 'install-sh' configure.ac:16: error: required file 'conf/ltmain.sh' not found configure.ac:15: error: required file 'conf/missing' not found configure.ac:15: 'automake --add-missing' can install 'missing' test/Makefile.am: error: required file 'conf/depcomp' not found test/Makefile.am: 'automake --add-missing' can install 'depcomp' autoreconf2.50: automake failed with exit status: 1
このようにたくさん怒られます。何を怒っているのかというと、AC_CONFIG_AUX_DIR([conf]) で指定したディレクトリ(詳細は automakeのマニュアル参照)に、補助ツールが入ってないじゃないか!と怒っているのです。
エラーメッセージの最後に「'automake --add-missing' can install 'depcomp'」とあるように、優しいautotoolsさんは解決策も用意してくれています。これもやってみましょう。
$ automake --add-missing configure.ac:16: installing 'conf/compile' configure.ac:16: installing 'conf/config.guess' configure.ac:16: installing 'conf/config.sub' configure.ac:15: installing 'conf/install-sh' configure.ac:16: error: required file 'conf/ltmain.sh' not found configure.ac:15: installing 'conf/missing' test/Makefile.am: installing 'conf/depcomp'
まだ何か怒っています。ltmain.shが無いそうです。これはlibtoolというツールの一部です。
最初に述べたようにこのプロジェクトは「動的ライブラリ」を作るプロジェクトです。GNU autotoolsは動的ライブラリをビルドする際にlibtoolという別のツールを使っています。プロジェクトのconfigure.acにLT_INIT() と書いていましたよね。この行はlibtoolを使うことをautotoolsに伝えるため(詳細は libtoolのマニュアル参照)にあります。
逆に言えば、LT_INIT() が無いと動的ライブラリはビルドできません。そんなの知るかって?正直言って私もそう思いました、けど、今のところそういう作りなので我慢するしかありません…。
さてltmain.shが何者かもわかったところで、肝心のどこから持って来るか?ですが、こんなときってfindが便利ですよね?
$ find /usr -name ltmain.sh /usr/share/libtool/config/ltmain.sh $ cp /usr/share/libtool/config/ltmain.sh conf/ltmain.sh $ automake --add-missing $ tree . |-- Makefile.am |-- Makefile.in |-- aclocal.m4 |-- conf | |-- compile -> /usr/share/automake-1.14/compile | |-- config.guess -> /usr/share/automake-1.14/config.guess | |-- config.sub -> /usr/share/automake-1.14/config.sub | |-- depcomp -> /usr/share/automake-1.14/depcomp | |-- install-sh -> /usr/share/automake-1.14/install-sh | |-- ltmain.sh | `-- missing -> /usr/share/automake-1.14/missing |-- config.h.in |-- configure |-- configure.ac `-- test |-- Makefile.am |-- Makefile.in |-- common | |-- Makefile.am | |-- Makefile.in | `-- utils.c `-- test.c
以上のようにltmain.shを無理やり持って来たら、エラーも警告も出なくなりました。めでたしめでたし、です。
もし他の環境に持っていく場合、conf/missingなどのシンボリックリンクはリンク先に何もない可能性がある(※)ので、実体に置き換えておくと良いでしょう。
(※)autotoolsをインストールしていない環境もあれば、インストールされていたとしても違うディレクトリに置かれている可能性があります。
既にautotoolsをお使いの方はもちろんのこと、autoreconfという見慣れぬコマンドに対して、すぐにヘルプを見た方はお気づきかと思いますが、今までの手順を一撃でやってくれるコマンドがあります。
その名もautoreconf --installです。早速やってみましょう。
$ tree . |-- Makefile.am |-- configure.ac `-- test |-- Makefile.am |-- common | |-- Makefile.am | `-- utils.c `-- test.c $ autoreconf --install libtoolize: putting auxiliary files in AC_CONFIG_AUX_DIR, `conf'. libtoolize: copying file `conf/ltmain.sh' libtoolize: Consider adding `AC_CONFIG_MACRO_DIR([m4])' to configure.ac and libtoolize: rerunning libtoolize, to keep the correct libtool macros in-tree. libtoolize: Consider adding `-I m4' to ACLOCAL_AMFLAGS in Makefile.am. configure.ac:16: installing 'conf/compile' configure.ac:16: installing 'conf/config.guess' configure.ac:16: installing 'conf/config.sub' configure.ac:15: installing 'conf/install-sh' configure.ac:15: installing 'conf/missing' test/Makefile.am: installing 'conf/depcomp' $ tree . |-- Makefile.am |-- Makefile.in |-- aclocal.m4 |-- conf | |-- compile | |-- config.guess | |-- config.sub | |-- depcomp | |-- install-sh | |-- ltmain.sh | `-- missing |-- config.h.in |-- configure |-- configure.ac `-- test |-- Makefile.am |-- Makefile.in |-- common | |-- Makefile.am | |-- Makefile.in | `-- utils.c `-- test.c
シンボリックリンクを実体に置き換えるところまで勝手にやってくれます。いやー、もう全部これだけで良いんじゃない?というくらい、素晴らしいです。
GNU autotoolsを使う時は、常にautoreconf --install頼りでもそこそこ動くはずですが、いざautoreconfがエラーで止まった時は手も足も出なくなってしまいます。
私個人の悲しい経験から言って、この手の便利ツールって、中で何をやっているのかサッパリわからないので、トラブルが起きると非常に苦労します。
ツールのコードを読めとは言いません(私も読んだことありません)が、せめてこのくらいは調べた方が良いです。トラブルシュートの早さが断然違います。
これらを調べるには、マニュアルを当てずっぽうに読むより、スクラッチからプロジェクトを作ってみるのが一番効きます。既存プロジェクトのコピペでは得られなかった、新たな発見があると思いますよ。
ソースコードのビルドシステムは多々あって決定打はない(個人的にはそう思う)のですが、比較的メジャーと言って良いのがGNU autotoolsです。ビルドする際にconfigureしてmakeするヤツは、大抵autotoolsが絡んでいます。
そんなGNU autotoolsを成すツール群の一つにautoconf/automakeと言うツールがあります。こいつらは簡単に言えばMakefileを作るツールです。
configure.ac --(autoconf)--> configure Makefile.am --(automake)--> Makefile.in --(configure)--> Makefile
最終的に生成されるのはMakefileなのに、なぜ直接書かず遠回りをするかというと、どんな環境でもそれなりに動くMakefileを直接書くのは、実は結構大変だからです。
例えば、依存関係にあるライブラリの有無をチェックしつつ、クロスコンパイルで動的ライブラリを生成するようなMakefileをパッと書けますか?私には無理そうですし、仮に書けたとしても、Makefileを作るのは手段であって目的ではないので、あまり時間を使いたくありません。
ビルドでお決まりの手順はツールで自動生成して楽して作りたい、これがGNU autotoolsや他のビルドツールを使う目的です。
こんな便利なautomakeですが、たまに仕様が変わって、以前の書き方だと怒られることがあります。
楽するためにツールを使っていたのに、ツールの仕様変更に対応するため時間を使うのは本末転倒な感じもしますが、それでもMakefileを直接書くより断然楽でしょうね。
昔のバージョンであるautomake-1.9ではこういう書き方が出来たのですが、
COMMON_DIR = ./common
common_SOURCES = $(COMMON_DIR)/utils.c
これがautomake-1.14だと、
test/Makefile.am:19: warning: source file '$(COMMON_DIR)/utils.c' is in a subdirectory, test/Makefile.am:19: but option 'subdir-objects' is disabled automake: warning: possible forward-incompatibility. automake: At least a source file is in a subdirectory, but the 'subdir-objects' automake: automake option hasn't been enabled. For now, the corresponding output automake: object file(s) will be placed in the top-level directory. However, automake: this behaviour will change in future Automake versions: they will automake: unconditionally cause object files to be placed in the same subdirectory automake: of the corresponding sources. automake: You are advised to start using 'subdir-objects' option throughout your automake: project, to avoid future incompatibilities.
こんな風に長々と説教されます。警告の通りsubdir-objectsを有効にすると、今度は $(COMMON_DIR) という名前のディレクトリが作られ、common/utils.cは無いと言われ、コンパイルエラーになります。
なんじゃそりゃ。
この警告を解決する方法は、サブディレクトリにもMakefile.amを置くこと、のようです。
$ tree . |-- Makefile.am |-- configure.ac `-- test |-- Makefile.am |-- common | `-- utils.c `-- test.c ---- configure.ac ---- AC_PREREQ(2.61) AC_INIT([automake_test], [0.1]) AC_ARG_PROGRAM() AC_CONFIG_SRCDIR([test/test.c]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_AUX_DIR([conf]) AM_INIT_AUTOMAKE([foreign]) LT_INIT() AC_CONFIG_FILES([Makefile test/Makefile]) AC_OUTPUT() ---- Makefile.am ---- SUBDIRS = test ---- test/Makefile.am ---- testdir = $(libdir) test_LTLIBRARIES = libtest.la libtest_la_SOURCES = test.c \ common/utils.c
このようなプロジェクトがあったとします。このプロジェクトはautomakeに、下記のように怒られます。
$ autoreconf --force test/Makefile.am:5: warning: source file 'common/utils.c' is in a subdirectory, test/Makefile.am:5: but option 'subdir-objects' is disabled automake: warning: possible forward-incompatibility. automake: At least a source file is in a subdirectory, but the 'subdir-objects' automake: automake option hasn't been enabled. For now, the corresponding output automake: object file(s) will be placed in the top-level directory. However, automake: this behaviour will change in future Automake versions: they will automake: unconditionally cause object files to be placed in the same subdirectory automake: of the corresponding sources. automake: You are advised to start using 'subdir-objects' option throughout your automake: project, to avoid future incompatibilities. autoreconf2.50: automake failed with exit status: 1
以下のように各ディレクトリにMakefile.amを作ってあげれば、警告は出なくなります。
. |-- Makefile.am |-- configure.ac★変更★ `-- test |-- Makefile.am★変更★ |-- common | |-- Makefile.am★追加★ | `-- utils.c `-- test.c ---- configure.ac ---- AC_PREREQ(2.61) AC_INIT([automake_test], [0.1]) AC_ARG_PROGRAM() AC_CONFIG_SRCDIR([test/test.c]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_AUX_DIR([conf]) AM_INIT_AUTOMAKE([foreign]) LT_INIT() AC_CONFIG_FILES([Makefile test/Makefile test/common/Makefile]) ^^^^^^^^^^^^^^^^^^^^ 追加 AC_OUTPUT() ---- test/Makefile.am ---- SUBDIRS = common test_newdir = $(libdir) test_new_LTLIBRARIES = libtest_new.la libtest_new_la_SOURCES = test.c libtest_new_la_LIBADD = common/libcommon.la ---- test/common/Makefile.am ---- noinst_LTLIBRARIES = libcommon.la libcommon_la_SOURCES = utils.c
最初は面倒くさいですが、やってみるとtestとtest/commonが分離されて見通しが良くなります。
従来の方法(親ディレクトリにMakefile.amを置く方法)と、新しい方法(各ディレクトリにMakefile.amを置く方法)の利点と欠点を考えてみます。
利点は、再利用性が高くなることです。
先の例で出てきたcommonディレクトリが「何らかの便利な機能を提供しているパッケージ」だとして、他のプロジェクトにcommonのコードを流用する場合を考えてみましょう。
従来のtest/Makefile.amに全て書く方法だと、commonディレクトリのソースコードは使いまわせますが、ビルド設定であるMakefile.amが親ディレクトリのtest側にあって、単純に使いまわせません。なぜならtest/Makefile.amではtestのビルド設定とcommonのビルド設定が混ざって書かれているため、commonのビルド設定だけを抽出するのが難しいためです。
これが新しいtest/Makefile.amとtest/common/Makefile.amに分ける方法だと、文字通りcommonディレクトリをコピーするだけでMakefile.amつまりビルドの設定も使いまわすことができます。
欠点は面倒くさいことです。
無意味にディレクトリを分割したがる輩には、ディレクトリごとにMakefile.amを作らなければならない面倒くささが、ある意味の抑止力になるので、欠点だとも言い切れませんが、やはり面倒くさいものは面倒くさいです…。
ちなみに従来の方法で書くと警告が出ますが、今のところ使えない訳でもない(※)ので、ちょっと試してみるだけとか、一時的なツールに使うだけであれば、従来の方法を選べば良いと思います。
(※)今は警告だけですが、そのうち廃止されるかもしれません。
< | 2016 | > | ||||
<< | < | 01 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | - | - | - | - | - | - |
合計:
本日: