Cプリプロセッサとアセンブラのマクロ

レジスタの初期化を最初はCのコードで書こうと思ったのだが、

まだスタックを使えない段階でレジスタの初期化をした方がよいとなって、

そうなるとアセンブラで書いた方が安全かもしれないなと思った。

所定の値をレジスタにセットするだけなのでアセンブラで書くこと自体は難しくない。

が、条件によってセットする値が変わるのが難点である。

(その条件自体はすでにアセンブラのコードで使っているものである)


で、今どきのアセンブラというのはCのプリプロセッサが使える。

定数の定義をCとアセンブラで共通のヘッダファイルにすることもできるし、

アセンブラよりも高度なCのプリプロセッサのマクロが使えるのもある。

今回のケースはまさにこの高度なプリプロセッサが活用できるものである。

設定するパラメータがCFG1, CFG2…と複数あり、

そのパラメータのセットが SET1, SET2…と複数あるので、下記のように定義しておく。

#define CFG_SET1_CFG1 0x11111111

そしてマクロの引数にセット名を取り、トークン連結演算子##を活用して CFG_[セット名]_CFG1 という定数名を作って、

その値をパラメータCFG1にセットするという処理を書けばよいわけだ。


一方のアセンブラのマクロはlocalに列挙したラベルを重複しないように処理するなど、

アセンブラの事情に即したマクロ展開が行われる。(cf. アセンブラのマクロ)

この2つのマクロ展開を組み合わせる場合、一体どうなるのか?

確認したところCプリプロセッサの処理が先に行われ、その後にアセンブラ処理になる。

当たり前ではあるのだが、アセンブラのマクロの引数をCプリプロセッサに処理させることはできないと。

上記の定数展開はアセンブラのマクロ処理より前に完了させなければならない。


で、いろいろ考えたのだがこんな形になった。

まず、パラメータを取ってセットするアセンブラのマクロを書く。

.macro CFGSET_RAW cfg1, cfg2
      LDR R0, =cfg1
      ...

このマクロは CFGSET_RAW 0x11,0x12 のように使うことができる。

で、このマクロを使うマクロをCプリプロセッサのマクロで書く

#define CFGSET(SET) CFGSET_RAW CFG_##SET##_CFG1, CFG_##SET##_CFG2

これでCFGSET(SET1)と書くと、CFG_SET1_CFG1, CFG_SET1_CFG2 という定数が呼び出される。

これらの定数を#defineで定義しておけば、その定数を展開してを引数に並べたCFGSET_RAWマクロ呼び出しが作られる。


さらにはこのCプリプロセッサのマクロを使ってアセンブラのマクロを書く。

.macro CFG_SELECT_AND_SET
     local COND1, COND2, END
     TST R0, #1
     BEQ COND1
     TST R0, #2
     BEQ COND2
     ... COND1:
     CFGSET(SET1)
     B   END COND2:
     CFGSET(SET2)
     B   END
     ... END: .endm

これも先にCプリプロセッサで展開が行われる。

CプリプロセッサのCFGSETマクロの引数を変えると、

展開される定数の異なるアセンブラのCFGSET_RAWマクロの呼び出しが作られる。

で、アセンブラではCFG_SELECT_AND_SETマクロの中に、CFGSET_RAWマクロが入れ子になった形になる。

アセンブラのマクロにアセンブラのマクロが入れ子になっているので問題ない。


思ったよりあっさり動いてしまい拍子抜けという感じもあるのだが、

Cプリプロセッサのマクロの処理順序は時に注意が必要である。

マクロの引数はトークン連結演算子##、文字列化演算子#がない限りは、

#defineの定数置き換えがあればまず置き換えが行われる。

#define SQU(X) (X)*(X)
#define TWO 2

ここでSQU(TWO)と書くと、まず引数のTWOを2と解釈して、Xに2を当てはめるという扱いになる。

XにTWOを当てはめて(TWO)*(TWO)としてから、TWOを2に置き換えるわけではない。

この場合はどちらの順序でやっても差は見えないが、トークン連結演算子や文字列化演算子との組み合わせで問題になる。


上記の定数をCのプログラムで利用することを考えるとこんなマクロを作る可能性がある。

#define SETCFG1(SET)    cfg[1]=CFG_##SET##_CFG1
#define SETCFG2(SET) cfg[2]=CFG_##SET##_CFG2 #define SETCFGS(SET) do{ SETCFG1(SET); SETCFG2(SET); }while(0)

それでSETCFGS(SET1)としても思った通りには動いてくれない。

なぜならばSETCFGSマクロの解釈時に、まず引数のSET1を解釈しようとするから。

もしも #define SET1 100 とか定義されていたら、SETを100に置き換えて、

SETCFG1(SET)→SETCFG1(100)→cfg[1]=CFG_100_CFG1 となる。


こういう目的をもってマクロを入れ子にする場合もあるのだが……

#define STR2(X)          #X
#define STR(X)           STR2(X)
#define SAYCFG(SET,CFG)  puts( #SET "." #CFG "=" STR(CFG_##SET##_##CFG) )

SAYCFG(SET2,CFG2)と書くと SET2.CFG2=に続いて CFG_SET2_CFG2 で定義した定数がputsで出力されるマクロである。

#SETは引数SET2をそのまま文字列化して “SET2” に置き換えられる。

これはよいのだが、CFG_SET2_CFG2を定数の文字列にして表示するには、

STRマクロの引数とすると、トークン連結演算子, 文字列化演算子で使われない引数ということで解釈され、

CFG_SET2_CFG2が#defineで定義した定数(例えば0x02)に置き換えられる。

それをSTR2マクロの引数に渡すとその定数を文字列化して、”0x02″ のような文字列が得られると。

これを2段階にせず、直接STR2マクロの引数にすると “CFG_SET2_CFG2” という文字列を生成されてしまう。


こういうのはあまり一般的な話ではなく、文字列を結合させるというのは、

結合させた名前の定数の値を直接利用したいからだろうと。

一番外側のマクロで定数の値が展開されるようになっていればよいと。

ただし、その定数を他のマクロに渡す場合、引数を##や#で使っていないことが前提である。

最後に書いたような特殊なケースでは入れ子にしてあれこれする。

展開して文字列化するというのはデバッグ目的では多少あるかもしれない。

でも、今回の本題のアセンブラではまず関係ないですけどね。