はじめに
今回は、組み込み開発でよく使用されている関数ポインタについて、定義方法から使い方まで説明してみたいと思います。
この記事の対象読者
- 学校などでC言語の基礎知識を学んだことがある方
- ポインタ変数を理解している方
この記事を理解する上での前提知識
- 変数や関数はメモリ上に存在している
- メモリ上のアドレスを取得したい場合は、変数名や関数名に&を付ける
- 構造体のメンバー変数は、構造体の変数名.メンバー変数名でアクセスできる
関数ポインタとは?
関数のアドレスを格納する変数です。
関数ポインタの定義方法
関数ポインタを定義するときには以下の3つを考える必要があります。
① 関数のアドレスを格納する変数名
② ①が参照する先の関数の戻り値
③ ①が参照する先の関数の引数の個数とデータ型
例えば、①がg_functionPointer、②がuint8_t、③の個数が2、データ型が2つともuint8_tの場合、以下のように定義します。
uint8_t (* g_functionPointer)( uint8_t, uint8_t )
注意点として、②と③両方とも一致していないと①から呼び出すことができません。
上記の例を用いると、呼び出し先の関数について、②はあっているが、③があっていない(例えば、引数の個数が0でvoid型など)もしくは、③はあっているが②はあっていない(例えば、戻り値がdouble型など)です。
関数ポインタは配列と構造体と組み合わせて使われることが多いです。
次から例を見ていきましょう。
例①:関数ポインタと配列を組み合わせる
#include <stdint.h>
#include <stdio.h>
#include "Ex1.h"
// 関数ポインタの定義
uint8_t (* g_functionPointer[2] ) ( uint8_t, uint8_t ) = { &funcA, &funcB };
int main()
{
uint8_t value1 = 0;
uint8_t value2 = 0;
// funcAの呼び出し
value1 = g_functionPointer[0](3,5);
// funcBの呼び出し
value2 = g_functionPointer[1](3,5);
// value1の値を表示
printf("value1の値:%d\n", value1);
// value2の値を表示
printf("value2の値:%d\n", value2);
return 0;
}
uint8_t funcA(uint8_t a, uint8_t b)
{
return a + b;
}
uint8_t funcB(uint8_t a, uint8_t b)
{
return a * b;
}
#include <stdint.h>
uint8_t funcA(uint8_t a, uint8_t b);
uint8_t funcB(uint8_t a, uint8_t b);
value1の値:8
value2の値:15
例②:関数ポインタと構造体を組み合わせる
#include <stdint.h>
#include <stdio.h>
#include "Ex2.h"
stExample g_functionPointer[2] = {
{funcA1, funcB1},
{funcA2, funcB2}
};
// グローバル変数の定義
uint8_t g_number1 = 0;
uint8_t g_number2 = 0;
int main(void)
{
// ローカル変数の定義
uint8_t value1 = 0;
uint8_t value2 = 0;
// 値をセットする関数の呼び出し
(*(g_functionPointer[0].setValue))(3);
// 値を表示する
value1 = (uint8_t)(*(g_functionPointer[0].getValue))();
printf("g_number1の値:%d\n",value1);
// 値をセットする関数の呼び出し
(*(g_functionPointer[1].setValue))(5);
// 値を表示する
value2 = (uint8_t)(*(g_functionPointer[1].getValue))();
printf("g_number2の値:%d\n.", value2);
return 0;
}
void funcA1(uint8_t number)
{
g_number1 = number;
}
uint8_t funcB1(void)
{
return g_number1;
}
void funcA2(uint8_t number)
{
g_number2 = number;
}
uint8_t funcB2(void)
{
return g_number2;
}
typedef struct{
void (* setValue )( uint8_t );
uint8_t (* getValue )( void );
}stExample;
void funcA1(uint8_t number);
uint8_t funcB1(void);
void funcA2(uint8_t number);
uint8_t funcB2(void);
g_number1の値:3
g_number2の値:5
関数ポインタの定義方法と呼び出し方のまとめ
- 定義時に関数名の前に&をつけると、呼び出し時は変数名だけで呼び出せる(例①のパターン)
- 定義時に関数名の前に&をつけないときは、呼び出し時は変数名の前に*を付けて呼び出す(例②のパターン)
おわりに
関数も変数と同じようにメモリ上に存在していることが分かれば、関数ポインタを理解することも難しくないかと思います。
関数ポインタの使いどころとして、組込み開発では、コールバック関数で使われています。
これについてはまた別に記事にしようと思います。
最後までご覧いただきありがとうございました。