マイコンのとあるプログラムのビルド結果を確認していると、
なんかえらいスタックを確保しているところがある。
そんなスタックを使うような処理ではないはずと詳しく見てみると……
void func(unsigned long *ptr){
const unsigned short Tbl[]={0x0001, 0x0002, 0x0004 ... };
といった記述をした定数テーブルをわざわざスタックにコピーしているようだった。
Cにおけるconstの効力というのはかなり難しいのだが、
この場合はTbl[]の値を書き換えできないことを表す。
初期値から書き換えできない変数はROMに割り付けることができる。
なのでROM上にデータ構造を置きたいときは const を付けたグローバル変数として記述することが多い。
ローカル変数でもそうだと思ったのだが、なぜかそうならない。
この問題は “const” を “static const” にすることで解決する。
こうするとテーブルの値をROMに配置して直接アクセスするようになった。
ローカル変数にstaticと付けると、プログラムの実行中はずっと確保される。
staticを付けた変数の初期値は起動時に1回だけ初期化される。
よって初期値付きのグローバル変数と同様に処理されることになる。
ここにconstを付けることで初期化時から不変であることを示すので、
起動時から不変=ROMに割り付けることができるという判断になるよう。
確かに関数内のローカル変数の初期値は一般的には関数の中で初期化処理が行われる。
もしconstを付けない場合は、ROM上の値をスタックにコピーする挙動は妥当である。
constを付けた場合は、その関数内の初期化時から不変であることを表す。
ただ、関数が何度実行されても初期値が変わらないというならば、
それは起動時から不変=ROMに割り付けられるという判断は可能だし、
そのような判断を行うコンパイラも世の中にはあるはず。
でも、static constとconstで挙動差のあるコンパイラは多いらしい。
明確なところはわからないところはあるが、constを付けた配列を他のルーチンに渡す場合、
渡した先で不変であるというのが徹底されず、書き換えられることを心配しているのかもしれない。
ROMに配置すればどうやっても書換はできないが、
起動時にROM→RAMに展開するシステムだと書き換えようと思えば書換できてしまう。
そうなった場合でも、スタックへのコピーを行っていれば、次回関数呼び出し時には影響しない。
constを付けたのに書き換えされてしまうというリスクは static const でも同じだと思うが、
“static” と付けたことで、起動時に1回だけ初期化するよう明示されているので、
そのようなリスクはすでに受け入れられているという判断になるのだろう。
うーん、その懸念と回避策は妥当なのか? と気になるが、実務上はそのようにしていることが多いよう。
Cだと #define で定数を宣言することが多いが、こういう記法もある。
const unsigned int MASK = 0x00FFFFFFu;
ローカル変数でこのような書き方をする場合、コンパイラで定数として取り込まれてしまうことを期待している。
ROM上にこの値を置いて、それをレジスタにロードして使うようなこともあるだろう。
同様の理由で static const と記載した方がよいのかもしれないが、
ポインタとして使うことがなければ、おそらくは差は出ないだろう。
僕が扱っているところではこういうコードはないんですけどね。