これは自分用メモ。
ご存知C言語にはマクロと呼ばれる#define
などを使ったルール記述が可能だ。有名なものだと#define
, #ifdef
, #endif
などのものだ。これを使えばコンパイル時に様々なオプションでソースコードを改変することができ、グローバルに持たせておきたい値を設定するときに有効だったりする(まあプリプロセッサによる下処理とか現代的なプログラミング言語から見てみるとあまり便利じゃなかったりとかあるかもしれないが、それは目をつぶる)。
このマクロは定数を定義するだけでなく、簡単な関数のように使えるところが便利だ。例えばデバッグビルドとリリースビルドでprintf()
を挿入したいかどうかを決めるとき、
#ifdef DEBUG #define DEBUG_PRINT(x) fprintf(stder, x); #else // DEBUG #define DEBUG_PRINT(x) #endif // DEBUG
みたいな感じで定義しておけばデバッグビルド(DEBUG
有効時)のみDEBUG_PRINT("hello")
で出力が行われ、リリースビルド時にはそのメッセージは消える、みたいなことができるようになる。このように、関数ほどではないにせよ、引数を取って簡単な簡単な処理を記述できるような、簡単な関数を作ることができる機能がある。
これは、実は私たち低レイヤプログラマにとってみれば地味に便利な機能だったりする。このマクロはC言語だけでなく、gcc経由でアセンブリファイルにも適用可能だからだ。
test.S
#define TEST_LD( load_inst, addr_reg) load_inst x10, 0(addr_reg) _start: TEST_LD(ld, x20) TEST_LD(lw, x21)
上記のようなアセンブリファイルを、直接as
に流すのではなくgcc
を経由してアセンブルすることでいったんプリプロセッサを通すことができる。最終的に以下のようなアセンブリファイルとしてアセンブルされる。
$ riscv64-unknown-elf-gcc test.S # gcc(コンパイラドライバ)を経由することでプリプロセッサが動く # さらに注: この方式は拡張子が.Sの場合に有効で、.sの場合には使えないとのコメントを頂いた。ありがとうございます。
_start: ld x10, 0(x20) lw x10, 0(x21)
さらに便利なのは、連結演算子により文字の連結を行う機能だ。C言語のマクロには#
と##
という演算子があり、詳細は以下がとても分かりやすい。
#
演算子はダブルクォーテーション付きで文字を連結する。詳細は上記リンク。
##
演算子はダブルクォーテーション無しで文字を連結する。上記のアセンブリファイルに対するマクロの適用では、経験上こちらの方が良く使う。
#define V_LD_ELEM( EEW, vreg, base_addr_reg ) vle ## EEW ##.v vreg, base_addr_reg V_LD_ELEM(8, v10, x10) V_LD_ELEM(16, v11, x11) V_LD_ELEM(32, v12, x12) V_LD_ELEM(64, v13, x13)
上記のプログラムは以下のように処理される。このように、命令の生成など、様々な命令のバリエーションの生成で使用することができる。検証パタンの大量生成において、かなり便利だ。
vle8.v v10, x10 vle16.v v11, x11 vle32.v v12, x12 vle64.v v13, x13