もう一人のY君

iPhoneアプリのレビューやアップデートレビューなどを書いています. たまに数学の記事も書きます.

もう一人のY君 MENU  MENU

(健忘録)ビットによるフラグ処理

 こんにちは, @the_theorierです.

 

 今回は先日仕事でやったことの健忘録です, ちなみにBCBなのでCなどとはちょっとだけ表現が違う場所もあります(考え方は同じです).

 

ビットを扱うということ

 そもそもフラグは, 開発者が好きに決めて良いわけです, 無難にint型でもdoubleでも.

 ただフラグ一つにつき変数一つ…とやっていくと, データ処理がどんどん多くなっていきます, それが通信を要する場合だとそっちの処理ばかりが先行して肝心のパラメータなどのデータのやりとりをする前にタイマーが終わってしまいます.

 

 いくら高速で処理できるようになったとはいえそこで妥協するのは意味が無いですし, ボーレートを上げすぎた状態で一度に送るデータが多いとこれもこれで処理が追いつきません.

 

 そもそもフラグというのは立ったか降りたか…の二つですから, それに1バイト2バイト…と使うのも勿体ないです.

 

 一つにまとめれば処理もそれだけ早くなります.

 

 

 …というわけで今回は16bit int で考えてみます.

 

 16bit なので

 

□□□□ □□□□ □□□□ □□□□

 

と16個分のビット(0または1)が用意されています, この16個の一つ一つをフラグと見做すわけですね.

 

 取り敢えず適当にフラグ用の変数を定義しておきましょう.

 

int BIT_FLG;

 

 上の通りで一つの int 変数に最大で16個分のフラグを割り当てられることになりますね.

 例えば1ビット目はフラグ1個目, 2ビット目はフラグ2個目…16ビット目はフラグ16個目…という感じです.

 

 フラグ1だけが立ってる状態ならBIT_FLGは 0000 0000 0000 0001 ですね, 1, 2, 3が立っているなら 0000 0000 0000 0111 という感じです.

 

 ソースでビット表記しても良いんですが, 大抵は16進数(HEX)で書きます.

 16進の表現は頭に 0x を付けることになっており, 例えば 0x0001 (0x1でも良いと思います)は 0000 0000 0000 0001 であり, 0000 0000 0000 0111 なら 0x0007 になります.

 

 既に表現してある通り, 2進から16進への変換は, ビットを後ろから4桁ずつ区切り, それぞれを16進に変換するとやりやすいです.

 例えば 0110 1100 0101 1110 をHEXにするなら, 0110 は HEXで 4+2=6, 1100は8+4=12なのでc, 0101 は 4+1=5, 1110 は 8+4+2=14なのでe, よって 0x6c5e…となります.

 4桁の2進を16進にする手間で済むので多少楽ですね, 逆変換も上のやり方を逆にするだけです.

 

 これに従ってフラグ1から16までを振り分けると下のようになります.

 

フラグ1 → 0x0001 (= 0000 0000 0000 0001)

フラグ2 → 0x0002 (= 0000 0000 0000 0010)

フラグ3 → 0x0004 (= 0000 0000 0000 0100)

フラグ4 → 0x0008 (= 0000 0000 0000 1000)

フラグ5 → 0x0010 (= 0000 0000 0001 0000)

フラグ16 → 0x8000 (= 1000 0000 0000 0000)

 

 例えば BIT_FLG が 0x01f1 であるとします, これは2進に直すと 0000 0001 1111 0001 なのでフラグ1, 5, 6, 7, 8, 9 が立っている…ということになります.

 

 

 ここまでくれば後は比較的楽ですね, それぞれのフラグに応じて処理を行わせればOKです.

 

フラグを使った操作

 以降はBCBに準ずることが多いのでちょっと毛色が違う…と思うかもしれません.

 

 例えば何らかのコンポーネント, 標準のボタンでも良いですが, それをクリックしたらフラグ1を立てるようにでもしましょう.

 

void __fastcall TForm1::Button1Click(TObject *Sender)

{

  BIT_FLG |= 0x0001;      //フラグ1を立てる

}

 

 

  BCBでは対象のフォームだけ…だったり関数の中だけで使ったりとある意味で自由に変数宣言が出来ますから, 基本的にグローバルで使いたいフラグについてはヘッダファイルのpublicで宣言するなりしておきます.

 

 またルーチンワークで制御する場合はTimerコンポーネントを利用するのが定石だとは思います, 今回作ったフラグも交えてサクッと書いてしまうと下のように出来ます.

 

//ルーチンインターバルタイマ

void __fastcall TForm1::Timer1Timer(TObject * Sender)

{

  RControl();    //ルーチンコントロール

}

//------------------------------------------------------

//ルーチンコントロール関数

void __fastcall TForm1::RControl(void)

{

  if(BIT_FLG & 0x0001){

    /*   フラグ1が立ったことによる処理   */

  }

  else if(BIT_FLG & 0x0002){

      /*   フラグ2が立ったことによる処理   */

    }

  else if(BIT_FLG & 0x0004){

      /*   フラグ3が立ったことによる処理   */

    }

   …

  else if(BIT_FLG & 0x8000){

      /*   フラグ16が立ったことによる処理   */

    }

}

 

 フラグは必要に応じてどこかで降ろさないといけません, そのタイミングは内容によって様々です, 通信であれば受信が完了したタイミングで行うのが適切でしょう.

 

 フラグを降ろす操作は色々考えれると思いますが, 例えば 0001 となっているビットフラグのはじめの1を降ろすには, 1110 とアンドを取ってやれば良いことになります(0000 = 0 とアンドを取ってしまうと, 他のフラグまで降りてしまうのでそれは避けたいです).

 1110 はHEXでeですから, 0x1 & 0xe とすればはじめのフラグだけが降りてくれます.

 BIT_FLGの方で考えれば

 

BIT_FLG &= 0xfffe

 

とすれば良いことになります.

 ここで 0xfffe は2進で 1111 1111 1111 1110 なわけですね.

 

 

 また通信などでは一度に送るデータ量が多いこともあります.

 大抵は分割したりすればいいんですがそれでも転送の間は他の処理は待たせておくのが良いものです.

 

 なので適当にカウンタを用意して各処理毎に値を振ってルーチンが回る毎に1減らし, 0になったら各々の処理が出来るようにする方法もあります.

 

 具体的に簡単に書くと下のようになります.

 

-ヘッダ(public)-

  int ReadyCounter;     //処理待ちカウンタ

 

-ソース-

//メインフォーム表示(イニシャル)

void __fastcall TForm1::FormCreate(TObject *Sender)

{

  ReadyCount = 0;

}

 

//ルーチンインターバルタイマ

void __fastcall TForm1::Timer1Timer(TObject * Sender)

{

  RControl();    //ルーチンコントロール

}

//------------------------------------------------------

//ルーチンコントロール関数

void __fastcall TForm1::RControl(void)

{

  if(ReadyCount != 0){      //待ちカウンタが0でない?

    --ReadyCount;      //待ちカウンタを一つ減らす

  }

  else{    //待ちカウンタが0?

    if(BIT_FLG & 0x0001){

      SampleCom1();       // フラグ1が立ったことによる処理 

    }

    以下フラグ処理…

  }

}

//-----------------------------------------------------------

//フラグ1が立ったことによる処理

void __fastcall TForm1::SampleCom1(void)

{

  ReadyCounter = 2;    //例えばこの処理の待ちカウンタは2にセット

  /*   その他の処理   */

}

 

 こうすることで, 待ちカウンタが0になるまではTransControl関数ではReadyCounterを1減らす処理だけが行われ, 他処理はスルーされます.

 データ量が少なければ0にしても問題ありません.

 

 150707_01

  簡単に流れをフローで書くと上のようになっています.

 

 

 正直まだHEXすらもまともにマスター出来てないので, 分かる範囲で忘れないように書いただけなので間違いは多々あると思います, その辺はご勘弁を…