忍者ブログ
ようこそ!JQ2RVNのブログです。 PICを使っていろいろな物を作ろうと画策中です。
×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。

C言語の関数で2つ以上の返値を出したいときや、
もともとの変数を直接いじりたいときには、
ポインタを使うのが定石だと思います。



通常のC言語での使い方は・・・・

void func( int* a )
{
    *a=....;
       (この後の例では、*a++;を使います)
}

呼び出しは
func( &x );

となります。

しかし、CCSCの場合は、これではうまく動かないようです。

アセンブラリストを見ると次のようになっています。


まずは呼び出し方。
MOVLW  32    (32は変数xのアドレス)
MOVWF  33   (33はfunc関数の引数aのアドレス)
GOTO     060 (func関数の番地)

(32や33、060という値は、コンパイラが自動的に変更してくれます。)

「33番地に、変数Xのアドレス(32)を入れて、func関数に飛ぶ」
となるので、参照動作(&がついた時の動作)は問題ないようです。

次に、func関数
060   MOVF 33,W
061   INCF 33,F
062   GOTO xxxx

これを見ると変なことがわかります。
061は関数内の処理、a++;に相当するのですが、
それがINCF 33,Fとなって、33番地の内容を直接インクリメントしています。
はじめは32が入っていたので、1プラスされて33になるだけなので、
予期していた動作と違うことがわかると思います。

結論として、ポインタ呼び出しはCCSは使えないことになります。





さて、これでは使いにくいので、次のような方法があります。

void func( int8 &a )
{
    a++;
}

呼び出し側は、
func( a );

この場合のアセンブラリストは・・・

呼び出し側:何も記載がない(呼び出す直前のプログラムメモリは0C3)
関数側:
0C4     INCF 32,F

つまり、コンパイラは関数のコールやジャンプなしに、
直接0C4番地にインクリメント命令を出しています。

なるほどといえば、なるほどな処理ですね。

もし、2か所以上から関数の呼び出しをしている場合はどうなるか?
064     INCF 36,F
0C4    INCF 32,F
というように、同じ処理が2つ書かれます。

なんてことはない、インライン展開をしてるわけですね。



インライン展開にはソースの可読性もよくなり、
ポインタを意識せずとも、関数を使うことができます。

しかし、必然的にプログラムメモリの使用量は増えるので、
複雑な処理を、何か所からかコールするという場合には不適です。

その場合は、グローバル変数を用意しておいて、
そこに値を書き込んでいく処理
が必要になります。





なお、MPLAB Cの場合はポインタ変数が使えますから、
最初の例のままでOKです。
PR

CCS Cコンパイラを使っている時、
条件文が、アセンブラに翻訳されるかを調べてみました。
さらに、どのように書けば最適であるかも調べました。

まず、以下のような処理を考えます。

UARTで8ビットのデータを送信するとき、
8ビット分は処理Aで、ストップビットを処理Bで行う。

通常ならば、while文を使うところですが、
タイマーの割り込みを使っているなどの理由で
if文にしないといけないということにしておきましょう。

順当に書けばこんな感じでしょうか?

i++;

if( i > 8 )
    処理B
else
    処理A

しかし、この場合if( i > 8 )の計算のために
引き算をしてキャリービットを検査するなど、
かなり面倒な条件処理になります。

 


 

逆にiを減算していったほうが、処理としてはスマートになります。
初期値としてi=8を与えておいて

i--;

if( i == 0 )
    処理B
else
    処理A

こうしておくと、
decf i,f
btfsc status , z
goto else
となって、2命令で条件分岐できそうと考えます。
が、しかし、そうはなってくれません。

どうやら、i==0と比較しようとすると、
8ビット幅でチェックしようとするみたいで、
結局XOR命令を使ってしまいます。
( A XOR B = 0 ならば A = B を使う)

 


 

このあたりを考慮すると、if文内はビット演算として書かないといけないようです。
つまり、比較演算子(==とか>とか<とか)を使わずに、
if( i ) のような書き方をすべきなのです。

さらに、デクリメント演算はif文の中に入れてしまう。
ただし、0の時の処理はif文直下(else内ではないという意味)に来るようにする。
つまり、if( ! --i )とします。

まとめると、

if( ! --i )
    処理B
else
    処理A

と書きます。

コンパイルされたアセンブラは以下のようになります。

  decfsz i,f
  goto else

  処理B
  goto end_if

else

  処理B

end_if

decfszは、
iから1ひいたものをiに入れ、その値が0なら次の命令を飛ばす
というものです。
1つの命令で、mov命令もsub命令も、条件判定もしてくれるので
高速でROMの節約にもなります。

 


 

論理を逆にして
if( --i )
    処理A
else
    処理B
としても良さそうですが、
コンパイラは処理A → 処理Bの順で翻訳するので(自動的に入れ替えてくれない)
余計な処理が入ります。

 


 

ポイントをまとめると

  • 回数の決まった処理では減算カウントをする
  • デクリメントの処理はif文のなかに
  • 0になった時の処理はifブロックに、0以外ではelseブロックに
  • 結果的に条件節は否定(!)になる。
  • アセンブラリストでdecfszに変換されているかチェックする。
というようになります。

 

この原則を守ると、可読性が失われる時もありますが、
処理の高速化やROMの節約にもなります。


コーディング時に考えるのは難しいので、
後でアセンブラリストを見て、考えるのでもいいと思います。



忍者ブログ [PR]
カレンダー
03 2024/04 05
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
プロフィール
コールサイン:
JQ2RVN
性別:
男性
自己紹介:
PICの開発を勉強中です。
目標はPICTNCの高性能版を作ること。
まずは、本家を解析しています。
最新コメント
[04/23 Eolande]
[04/16 太田和巳(JF2UJG)]
[09/10 je7ifp]
[08/06 JQ2RVN]
[08/03 OVC]
最新トラックバック
バーコード
ブログ内検索
アクセス解析