今更volatile(なポインタ)ではまるなんて
AVR Studio上で、割り込みベクタで処理されるリングバッファの状態(ポインタの指す先)をループ条件にしたコードを書いたんですよ。以下のようなイメージ。
while (ポインタ同士の比較) { }
これが一向にループを抜けてこない。ビルド時に生成される、アセンブリ言語が混ぜ込まれたファイル(*.lss)を見ると、条件判定の前まで戻ってくるループになっていませんでした。最初に条件判定して、するりと抜けられなければ無限ループに飛びます。
(略:このへんにポインタ比較するコードがある) 39a: 14 f1 brlt .+68 ; 0x3e0(中略) 3e0: ff cf rjmp .-2 ; 0x3e0 :飛び先
volatile unsigned char *hoge のような宣言をしているポインタです。いきなりAVR StudioのCコンパイラを疑ってしまいましたが、同様のコードをCygwinのgccに食わせても、最適化をかけると同様な結果になりました。つまり書き方がおかしいと。
それでGoogleで検索して今更理解したのが…
volatile int * hoge1; // ポインタの参照先がvolatile int * volatile hoge2; // ポインタがvolatile。今回必要だったのはこっち
そうか。ポインタだから、constと同様に、修飾子を書ける場所は前と後ろの2箇所にあったんですね。恥ずかしい話だけど、またいずれ自分で忘れそうなので書いておきます。
無事、ループのたびに判定部分まで戻るコードができました。
3d6: (ポインタ比較するコードの先頭) (中略) 3ee: 9c f3 brlt .-26 ; 0x3d6
AVRのポインタ
8bit演算のAVRなのにアドレスは16bitあり、アドレス演算をするとロード/ストアも加減算も大袈裟なコードが必要です。今回の件でコンパイル結果を確認するまで、すっかり頭にありませんでした。他の箇所をよく見たら、インクリメントですら上位バイト下位バイトそれぞれにロード・加算・ストアしているので6命令食ってる。パイプラインの仕様を細かくは読んでないけど、ロード/ストアは2Clockと書いてあるから、実行時間は命令数以上にインパクトがあるかも。
; ポインタ++ 1f8: 20 91 a6 00 lds r18, 0x00A6 1fc: 30 91 a7 00 lds r19, 0x00A7 200: 2f 5f subi r18, 0xFF ; 255 202: 3f 4f sbci r19, 0xFF ; 255 204: 30 93 a7 00 sts 0x00A7, r19 208: 20 93 a6 00 sts 0x00A6, r18 ; char型変数++ d8: 80 91 a4 00 lds r24, 0x00A4 dc: 8f 5f subi r24, 0xFF ; 255 de: 80 93 a4 00 sts 0x00A4, r24
1Byteの添字で表現できないような配列サイズを使う場合以外は、無闇にポインタを使わないほうが良さそうですね。動作はしますが。
結局今作っているものではポインタを使わないことにしたので、whileループ条件内でポインタの比較をするようなコードは消滅しました(´-`)