AVR32のSRAM開始アドレス&NULLポインタ問題
今頃気が付いたが、現在ターゲットにしているAT32UC3B0256はSRAMがアドレス0から割り付けられている。つまり変更可能な変数を定義したとき、この変数がSRAM領域先頭に割り付けられると、変数のポインタが0となる。
一方NULLポインタは通常0であり、実際avr32-gccでは0が割り付けられる(逆アセンブルでも確認済み)。NULLのヘッダフィルにおける定義は以下のようになっている。
stddef.h, 400: #define NULL __null
上記の定義はGCC特有の定義だが、__nullはコンパイラ内部で0となっているようだ。
NULLポインタは、さまざまな関数で特殊なポインタとして利用されており、特殊な意味でNULLなのか、本来の変数ポインタとしてのNULLなのか、ソフトウェア側では判断がつかなくなる。
C言語の規約からすれば、NULLが0である必要はないのだから、avr32-gccでは0以外の値をNULLに割り付ける手もある。しかし0ならレジスタSRのZビットをチェックすればいいだけで、特定の値と比較するより処理が早くなるから、結論としては「RAMをアドレス0に割り付けるな」となる(これは他のCPUでも通じる話)。
intc.cの話でも書いたように、どうもAtmelのMCU開発者はソフトウェアというものを知らないらしい。
実際のところ、アドレス0に何が割り付けられているのか?スタンドアローン版のデフォルト・プロジェクトだとセクション.dataがあり、ソフト開発者の立場からすると目眩がする。これでバグ出したら、原因究明が大変そう。
今更SRAMのアドレスなど変えようもない。だからよく考えてハードを設計して欲しいものだが、ここではソフトウェア側での対処方法を考えたい。
一番楽な方法は、アドレス0に何も割り付けないことだ。いくつか方法があるが、中でも簡単な方法はリンカ・スクリプトでSRAM領域開始アドレスを+4だけずらし、サイズを-4縮めることである。当然1byteで十分なのだが、アライメントを取るため4byteとした。
9: MEMORY 10: { 11: FLASH (rxai!w) : ORIGIN = 0x80000000, LENGTH = 256K 12: CPUSRAM (wxa!ri) : ORIGIN = 0x00000000, LENGTH = 32K 13: USERPAGE : ORIGIN = 0x80800000, LENGTH = 512 14: FACTORYPAGE : ORIGIN = 0x80800200, LENGTH = 512 15: }
12行目がSRAM領域なので、以下のように変更すればよい。
12: CPUSRAM (wxa!ri) : ORIGIN = 0x00000004, LENGTH = 32K-4
この方法の欠点は、絶対に使われることのないメモリが4byte出来てしまうことだ。
異なる方法としては、NULL判定されない変数のみを集めたセクション、あるいはそのような変数1つをアドレス0へ配置することだ。当然用途が確定しにくいヒープ領域やスタック領域を持ってきてはいけない。
この条件は意外に難しい。そんな変数をわざわざ見つけて異なるセクションに分けるのが面倒、そして後日メンテナンスでNULLチェックするよう修正する可能性があるかもしれない等々、あまり勧められない。
面倒なバグの元を潰せるコストとして、4byteは安いかもしれない。
ちなみにこの問題を好意的に解釈すると、内部高速SRAMを設計するにあたり、アドレス・デコーダが早くなるようアドレス0を開始したかったのだと思う。
それでもデフォルトのリンカ・スクリプトや例題でソフト開発者を導くように配慮が欲しいものだ。