switchを使ったステートマシンの実装と構造体配列を使ったステートマシンの実装

ステートマシンを実装するとき、一次元の状態遷移であれば、switchを使うことが多いと思いますが、構造体配列を使ったステートマシンと比較したら、どうなのか、試してみました。

まずは、switchを使ったステートマシンの実装

#include "stdio.h"

// ステート番号定義
enum
{
	INIT,
	NORMAL,
	FAIL,
	STATE_NUM
};

// 関数宣言
static void statemachine_switch(unsigned int state);

// 関数定義
static void statemachine_switch(unsigned int state)
{
	switch (state)
	{
	case INIT:
		printf("Now in INIT state. (switch)\n");
		break;
	case NORMAL:
		printf("Now in NORMAL state. (switch)\n");
		break;
	case FAIL:
		printf("Now in FAIL state. (switch)\n");
		break;
	default:
		printf("Now in default state. (switch)\n");
		break;
	}
	return;
}

int main()
{
	unsigned int i = 0;
	for (i = 0; i < STATE_NUM; i++)
	{
		// switch caseのステートマシン関数呼び出し
		statemachine_switch(i);
	}
	getchar();
    return 0;
}

引数で受け取った数値をswitchで分けて、それぞれのステートの処理をしています。

構造体配列を使ったステートマシンの実装

#include "stdio.h"

// ステートマシン用構造体の定義
typedef struct
{
	unsigned int state;	//ステート番号
	void(*func)();		//実行する関数
}_st_StateMachine;

// ステート番号定義
enum
{
	INIT,
	NORMAL,
	FAIL,
	STATE_NUM
};

// 関数宣言
static void statemachine_structarray(unsigned int state);
static void sa_INIT(void);
static void sa_NORMAL(void);
static void sa_FAIL(void);

// テーブル定義
const _st_StateMachine statearray[STATE_NUM] =
{
	{INIT, (*sa_INIT)},
	{NORMAL, (*sa_NORMAL)},
	{FAIL, (*sa_FAIL)}
};

// 関数定義
static void statemachine_structarray(unsigned int state)
{
	int i = 0;
	for (i = 0; i < STATE_NUM; i++)
	{
		if (statearray[i].state == state)
		{
			statearray[i].func();
			return;
		}
	}
	// ステート番号と一致しなかった = default
	printf("Now in default state. (struct array)\n");
	return;
}

static void sa_INIT(void)
{

	printf("Now in INIT state. (struct array)\n");
	return;
}

static void sa_NORMAL(void)
{

	printf("Now in NORMAL state. (struct array)\n");
	return;
}

static void sa_FAIL(void)
{

	printf("Now in FAIL state. (struct array)\n");
	return;
}

int main()
{
	unsigned int i = 0;
	for (i = 0; i < STATE_NUM; i++)
	{
		// 構造体配列のステートマシン関数呼び出し
		statemachine_structarray(i);
	}
	getchar();
    return 0;
}

引数で受け取った数値と一致するステート番号が構造体配列のテーブルにあるか、forループで探して、一致するものがあれば、テーブルの対応する関数ポインタを使って、関数を実行しています。

ここで注目して欲しいのは、statemachine_switch()とstatemachine_structarray()の中身です。
statemachine_switch()ではステートや、それぞれのステートでの処理が増えると、関数の複雑度が上がります。
しかし、statemachine_structarray()ではステートや、それぞれのステートでの処理が増えても関数の複雑度が上がることはありません。
構造体配列のテーブルや、それぞれのステートでの処理関数が増えるので、コードは増えますが、ステートの追加による影響が、追加したステートの関数のみで済みます。(switchの場合は、statemachine_switch()まで影響を受ける可能性がある)
コードはシンプルであることが一番いいので私は構造体配列を使ったステートマシンのほうが良いと思います。

LinuxでBD再生メモ

備忘録がてら書く。
MakeMKVインストールして、

# ln -s /usr/lib/libmmbd.so.0 /usr/lib/libaacs.so.0

libbdplusはなくても再生できる模様。
ディスクを入れて、マウントする。

# mount /dev/sr0 /mnt

sr0の数字部分は変わるっぽいので、テキトーに試してみる。/mntにAACSとかBDMVとかがあるか見て、あればマウントOK

$ vlc bluray:///mnt

C言語マクロで関数ポインタを利用する

以前、マクロで関数を定義しているものを配列に入れて、関数ポインタのように順繰りに処理をさせたいことがありましたが、スキル不足でできませんでした。
マクロに関数ポインタを定義するような人は殆どいないのか、情報がありません。
だったら、自分でやってみればいいのでは? ということで、やってみることにしました。

まずは単純に、マクロに関数ポインタを定義してみます。

#include <stdio.h>

int func1(void);

#define macro_func1 (*func1)()


int main(int argc, char *argv[]) {
	macro_func1;
	return 0;
}

int func1(void) {
	printf("func1\n");
	return 0;
}

これで、マクロに関数ポインタを定義できることがわかりました。

次に、これを配列に入れることができるのか、試してみました。

#include <stdio.h>

int func1(void);
int func2(void);
int func3(void);

#define macro_func1 (*func1)()
#define macro_func2 (*func2)()
#define macro_func3 (*func3)()

int main(int argc, char *argv[]) {
	int macro_func_array[3] = {
		macro_func1,
		macro_func2,
		macro_func3
	};
	int i;

	for (i = 0; i < 3; i++) {
		macro_func_array[i];
	}
	return 0;
}

int func1(void) {
	printf("func1\n");
	return 0;
}
int func2(void) {
	printf("func2\n");
	return 0;
}
int func3(void) {
	printf("func3\n");
	return 0;
}

これでやりたいことはできました。
これをするくらいなら、変数に関数ポインタをそのまま入れるほうがいい気がするのでやる意味ないですね。

gnome環境で無線LANを使用する

netctlでwifi-menuが使用できれば接続はできるが、gnomeのネットワーク設定が使えないのでnetworkmanagerとnetwork-manager-appletをインストールする。

# pacman -S networkmanager network-manager-applet

netctlのサービスを無効にし、

# systemctl disable netctl.service

networkmanagerのサービスを有効にする。

# systemctl enable NetworkManager.service

すぐに無線接続したい場合は、
netctlのサービスを停止し、

# systemctl stop netctl.service

networkmanagerのサービスを実行する。

# systemctl start NetworkManager.service

無線設定

grubインストール後に、netctlとdialogとwpa_supplicantをインストールする。

# pacman -S netctl dialog wpa_supplicant

dhcpcdサービスを無効にし、

# systemctl disable dhcpcd.service

netctlサービスを有効にする。

# systemctl enable netctl.service

再起動後に、

# wifi-menu

コマンドが使えるはずなので、実行し、無線LANに接続する。