LoginSignup
5

More than 3 years have passed since last update.

C言語の関数型と関数ポインタ型の話

Last updated at Posted at 2020-07-02

C言語には、代入可能な変数で使う関数ポインタ型の他に、関数型も存在します。
関数型は、関数プロトタイプ宣言における、関数(変数)のための型です。

関数定義出現前に関数を呼び出すとき使用する関数プロトタイプ宣言は、変数宣言と同じく、C言語の宣言文(declaration)の一つです。

/* 関数ポインタ型変数宣言文 */
int (* add_p)(int, int);

/* 関数プロトタイプ宣言文 */
int add(int, int);

ちなみに、この関数名のaddも、C言語のシンタックス上では変数ですが、代入は不可能なものです(セマンティックでのエラー)。


そしてtypedef文も、宣言文です。関数ポインタ型と関数型のtypedef宣言は以下のようになります。

/* 関数ポインタ型のtypedef宣言文 */
typedef int (* op_p)(int, int);

/* 関数ポインタ型のtypedef宣言文 */
typedef int op_t(int, int);

typedef宣言で定義されたtypedefを用いて、先程の関数ポインタ型変数宣言文と関数プロトタイプ宣言文を記述することができます。

/* 関数ポインタ型変数宣言文 */
op_p add_p;

/* 関数プロトタイプ宣言文 */
op_t add;

後者のプロトタイプ宣言のためにtypedefを用いる事例は少なく、その存在を知らない人も多いかもしれません。


また、関数型のtypedef名を用いることで、関数ポインタ型を導出する事もできます。

/* 関数ポインタ型変数宣言文 */
op_t * add_p;

/* 関数ポインタ型のtypedef宣言文 */
typedef op_t * op_p;

ただし、関数型自体にconstvolatile(、restrict)といった型修飾子(qualifier)を併用することは仕様上は禁止です。もちろん、そのポインタについては、型修飾子をつけることは可能です。

/* 正しくない変数宣言文 */
const op_t * add_p = 0;

/* 正しい関数ポインタ型変数宣言文 */
op_t * const add_p = 0;

前者は、gccもclangも、コンパイルエラーではなくwarningレベルでした。
(ポインタの対象の型についたconst修飾は、代入では(やはりwarningありで)無視するからだろう)


関数型や関数型変数(プロトタイプ宣言)を、sizeofで利用することは不正です。

/* 不正 */
size_t ftsize = sizeof (int (int, int));

int add(int, int);
/* 不正 */
size_t fsize = sizeof add;

gccもclangもどちらもwarningを出すにとどまり、実行した時の結果は1になりました。


代入できないので、関数型は、関数の戻り値型やstruct/unionのメンバーとしては使用できません。

しかし、関数の引数では、関数型の引数を記述できます。
これは、関数の引数の配列型がポインタ型扱いになるのと同様、関数の引数の関数型もポインタ型扱いになるためです。

/* opは関数ポインタ型 */
int apply(int op(int, int), int v) {
  return (*op)(v, v);
}
typedef int op_t(int, int);

/* opは関数ポインタ型 */
int apply(op_t op, int v) {
  return (*op)(v, v);
}

参考: 警告なしコンパイル&実行可能な、関数型使用のコード例

func-type-example.c
/* cc -std=c89 -pedantic -Wall -Wextra func-type-example.c */

/* types for function (prototype)  */
typedef int op2_t(int, int);

/* function prototype declaration with typedef types */
static op2_t add, sub;

/* types for function pointer */
typedef op2_t * op2_ptr1;
typedef int (* op2_ptr2)(int, int);

/* function type in parameter as function pointer type */
static int apply(op2_t op, int a, int b) {
  return (*op)(a, b);
}

extern int main() {
  const op2_ptr1 padd = &add;
  op2_t * psub = ⊂
  return padd(10, apply(psub, 2, 3)); /* echo $? => 9 */
}

/* K&R C style definition */
int add(a, b) int a; int b; {
  return a + b;
}

/* ANSI C style definition */
int sub(int a, int b)  {
  return a - b;
}

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5