はじめに
以前、関わったプロジェクトで、状態遷移処理について実装および改造する機会が何度か存在した。
その度に、関数テーブルを使用した状態遷移処理のコードを読み解くのに時間を要してきた。
状態遷移処理に関わった際に、有用な書籍などを調査したが、なかなか見つからなかった様に思える。
そのため、C言語での状態遷移処理について備忘録として記載する。
C言語の状態遷移についてインターネットでは先人の方々が、より詳細に、より分かりやすく、本記述よりも優れた文章で説明がされている。
そのため、この稚拙な文章や粗悪なコードを見てストレスを溜める前に、他の有用な情報を参照した方が良いだろう。
C言語での状態遷移処理について
C言語での状態遷移処理にはいくつかの実装方法が存在する。
良く見る状態遷移処理の実装
良く見る実装方法として、以下の2つが挙げられる
(1)switch文
(2)関数テーブル(jump table, マトリクスタスク)
(1)switch文
switch文を使用した実装は各状態毎に実行される処理が把握しやすく、実装も容易となる
しかし、実装する状態が煩雑になるとswitch文が増加してしまう
実装例
実装例として以下のコードを示す
unsigned int g_status; /* 状態 */
switch (g_status) {
case STS_INIT:
/* 初期状態 処理 */
break;
case STS_001:
/* 状態:001 処理 */
break;
case STS_002:
/* 状態:002 処理 */
break;
case STS_003:
/* 状態:003 処理 */
break;
default:
/* default 処理 */
break;
}
各case(状態)で、受信するイベント処理を記載し、状態とイベントの2軸で処理を管理するのが、一般的だと考えられる。
メリットとデメリット
メリット
・実装が容易かつ可読性が高い
デメリット
・状態とイベントが増加すると処理の把握が難しくなる
・状態が増加するとswitch処理が増加し、コード量が増える
(2)関数テーブル(jump table, table jump)
関数テーブルを使用した実装は可読性が低下するが、
状態遷移表からのコードへの落とし込み時に対比がしやすく分かりやすいことが
特徴として挙げられる
状態管理には多くの場合、複数のイベント通知と状態を整理するためN×Nの状態遷移表を用いる
状態遷移表(2×3)の例を以下に示す
上記の状態遷移表では各状態時にイベント通知を受信した際の、遷移先の状態が記載されている
状態遷移表は、実装する機能の仕様整理や機能テストに重要となる
そのため、状態遷移の記載内容を漏れなくコードに落とし込む必要がある
状態遷移表のコードへの落とし込みでは関数テーブル(jump tableやtable jumpとも呼ばれる)を用いる場合が多い
かつて所属していたプロジェクトではマトリクスタスクという呼び方をしていた。
ここら辺の呼称はマチマチであるという印象である
関数テーブルの例を以下に示す
/* Function Table */
static CHGSTSTASK_FUNC chgStsTask_table[ STS_MAX ][ EVENT_MAX ] = {
{ chgStsTask_00, chgStsTask_01 },
{ chgStsTask_02, chgStsTask_03 },
{ chgStsTask_04, chgStsTask_05 },
{ chgStsTask_06, chgStsTask_07 }
};
関数テーブルを見ると状態遷移表の様に、横方向にイベント, 縦方向に状態を記載するため、
各処理時の実行関数が明確になる
実装例
以下に関数テーブルを使用した、状態遷移処理の実装例を示す
#include <stdio.h>
#include <stdlib.h>
#include "main.h"
#include "matrix_task.h"
int main( void ) {
int ret = 0;
int cnt = 0;
printf("\n");
printf("#### Status Change Prgram Start. ####\n");
gSts = STS_INIT;
for (cnt = 0; cnt < 5; cnt++ ) {
ret = eventNotice( EVENT_001 );
if ( ret != 0 ) {
printf("Err\n");
}
ret = eventNotice( EVENT_002 );
if ( ret != 0 ) {
printf("Err\n");
}
}
printf("#### Status Change Prgram End. ####\n");
/* show program result */
ret = showDebugLog();
return 0;
}
int setDebugLog( int status, int event) {
if ( gDebugCnt == DEBUG_DATA_MAX ) {
gDebugCnt = 0;
}
/* set time info */
timespec_get( &debugLog[gDebugCnt].ts, TIME_UTC );
/* set status info */
debugLog[gDebugCnt].debugSts = status;
/* set event info */
debugLog[gDebugCnt].debugEvent = event;
/* debug log index count up */
gDebugCnt++;
return 0;
}
int showDebugLog( void ) {
int cnt = 0;
char buff[100];
printf("\n");
printf("-------- SHOW DEBUG LOG ---------\n");
for ( cnt = 0; cnt < DEBUG_DATA_MAX; cnt++ ) {
strftime( buff, sizeof( buff ), "%F %T", gmtime( &debugLog[cnt].ts.tv_sec ) );
printf(" %s.%09ld UTC ", buff, debugLog[cnt].ts.tv_nsec);
printf("status:%d ", debugLog[cnt].debugSts);
printf("event:%d ", debugLog[cnt].debugEvent);
printf("\n");
}
printf("-------- DEBUG LOG END ---------\n");
printf("\n");
return 0;
}
#ifndef _MAIN_H_
#define _MAIN_H_
#include <time.h>
#define DEBUG_DATA_MAX 10
typedef struct debugMtx {
struct timespec ts;
int debugSts;
int debugEvent;
} debugMtx_t;
debugMtx_t debugLog[ DEBUG_DATA_MAX ];
int gDebugCnt;
int setDebugLog( int status, int event);
int showDebugLog( void );
#endif /* _MAIN_H_ */
#include "matrix_task.h"
#include <time.h>
/* task function */
int chgStsTask_00( int event ) {
/* fuction action */
printf(" Path Through %s Now Status:%d, Event:%d.\n", __FUNCTION__, gSts, gEvent);
if (event == EVENT_001 ) {
gSts = STS_001;
}
return 0;
}
int chgStsTask_01( int event ) {
/* fuction action */
printf(" Path Through %s Now Status:%d, Event:%d.\n", __FUNCTION__, gSts, gEvent);
return 0;
}
int chgStsTask_02( int event ) {
/* fuction action */
printf(" Path Through %s Now Status:%d, Event:%d.\n", __FUNCTION__, gSts, gEvent);
if (event == EVENT_001) {
gSts = STS_002;
}
return 0;
}
int chgStsTask_03( int event ) {
/* fuction action */
printf(" Path Through %s Now Status:%d, Event:%d.\n", __FUNCTION__, gSts, gEvent);
return 0;
}
int chgStsTask_04( int event ) {
/* fuction action */
printf(" Path Through %s Now Status:%d, Event:%d.\n", __FUNCTION__, gSts, gEvent);
if (event == EVENT_002) {
gSts = STS_END;
}
return 0;
}
int chgStsTask_05( int event ) {
/* fuction action */
printf(" Path Through %s Now Status:%d, Event:%d.\n", __FUNCTION__, gSts, gEvent);
if (event == EVENT_002) {
gSts = STS_END;
}
return 0;
}
int chgStsTask_06( int event ) {
/* fuction action */
printf(" Path Through %s Now Status:%d, Event:%d.\n", __FUNCTION__, gSts, gEvent);
if (event == EVENT_001 ) {
gSts = STS_001;
}
return 0;
}
int chgStsTask_07( int event ) {
/* fuction action */
printf(" Path Through %s Now Status:%d, Event:%d.\n", __FUNCTION__, gSts, gEvent);
if (event == EVENT_001 ) {
gSts = STS_002;
}
return 0;
}
int eventNotice( int notice_event ) {
int ret = 0;
int nowSts = gSts;
if (( EVENT_001 > notice_event ) || ( EVENT_MAX < notice_event )) {
/* err event */
printf("Event Err Notice.\n");
return -1;
}
/* Event Notice */
ret = (*chgStsTask_table[ gSts ][ gEvent ])( notice_event );
if ( ret != 0 ) {
return -1;
}
ret = setDebugLog( nowSts, notice_event );
if (ret != 0) {
return -1;
}
return 0;
}
#ifndef _MATRIX_TASK_
#define _MATRIX_TASK_
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "main.h"
/* status */
enum {
STS_INIT,
STS_001,
STS_002,
STS_END,
STS_MAX
} prgSts;
/* event */
enum {
EVENT_001,
EVENT_002,
EVENT_MAX
} eventNum;
/* global status */
static int gSts;
/* global event */
static int gEvent;
/* proto type */
int chgStsTask_00( int event );
int chgStsTask_01( int event );
int chgStsTask_02( int event );
int chgStsTask_03( int event );
int chgStsTask_04( int event );
int chgStsTask_05( int event );
int chgStsTask_06( int event );
int chgStsTask_07( int event );
typedef int ( *CHGSTSTASK_FUNC )( int event );
extern int eventNotice( int notice_event );
/* Function Table */
static CHGSTSTASK_FUNC chgStsTask_table[ STS_MAX ][ EVENT_MAX ] = {
{ chgStsTask_00, chgStsTask_01 },
{ chgStsTask_02, chgStsTask_03 },
{ chgStsTask_04, chgStsTask_05 },
{ chgStsTask_06, chgStsTask_07 }
};
#endif /* _MATRIX_TASK_ */
メリットとデメリット
メリット
・状態遷移表と
デメリット
・状態とイベントが増加すると処理の把握が難しくなる
・状態が増加するとswitch処理が増加し、コード量が増える
参考
上流工程で効く,「テストの考え方」第4回「状態遷移図」と「状態遷移表」で見えるもの
状態遷移図/表、すなわち設計をコードでテストする