今更volatile(なポインタ)ではまるなんて

AVR Studio上で、割り込みベクタで処理されるリングバッファの状態(ポインタの指す先)をループ条件にしたコードを書いたんですよ。以下のようなイメージ。

while (ポインタ同士の比較) {  }

これが一向にループを抜けてこない。ビルド時に生成される、アセンブリ言語が混ぜ込まれたファイル(*.lss)を見ると、条件判定の前まで戻ってくるループになっていませんでした。最初に条件判定して、するりと抜けられなければ無限ループに飛びます。

(略:このへんにポインタ比較するコードがある)
 39a:	14 f1       	brlt	.+68     	; 0x3e0 
(中略)
 3e0:	ff cf       	rjmp	.-2      	; 0x3e0  :飛び先


volatile unsigned char *hoge のような宣言をしているポインタです。いきなりAVR StudioのCコンパイラを疑ってしまいましたが、同様のコードをCygwingccに食わせても、最適化をかけると同様な結果になりました。つまり書き方がおかしいと。


それで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ループ条件内でポインタの比較をするようなコードは消滅しました(´-`)