最近、Cで関数ポインタを使用した経験があります。
それで、あなた自身の質問に答えるという伝統を続けて、私はその主題に素早く飛び込む必要がある人々のために、非常に基本的なことの小さな要約を作ることにしました。
最近、Cで関数ポインタを使用した経験があります。
それで、あなた自身の質問に答えるという伝統を続けて、私はその主題に素早く飛び込む必要がある人々のために、非常に基本的なことの小さな要約を作ることにしました。
回答:
私たちが指す基本的な関数から始めましょう:
int addInt(int n, int m) {
return n+m;
}
まず、2を受け取ってint
を返す関数へのポインタを定義しましょうint
。
int (*functionPtr)(int,int);
これで、関数を安全に指すことができます。
functionPtr = &addInt;
関数へのポインターが用意できたので、それを使用しましょう。
int sum = (*functionPtr)(2, 3); // sum == 5
ポインタを別の関数に渡すことは基本的に同じです:
int add2to3(int (*functionPtr)(int, int)) {
return (*functionPtr)(2, 3);
}
戻り値で関数ポインターを使用することもできます(これを続けると、乱雑になります)。
// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
printf("Got parameter %d", n);
int (*functionPtr)(int,int) = &addInt;
return functionPtr;
}
しかし、それを使用する方がはるかに良いtypedef
です:
typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef
myFuncDef functionFactory(int n) {
printf("Got parameter %d", n);
myFuncDef functionPtr = &addInt;
return functionPtr;
}
pshufb
低速なので、以前の実装はさらに高速です。x264 / x265はこれを広く使用しており、オープンソースです。
Cの関数ポインターを使用して、Cでオブジェクト指向プログラミングを実行できます。
たとえば、次の行はCで記述されています。
String s1 = newString();
s1->set(s1, "hello");
はい、->
そしてnew
演算子の欠如は完全に無料ですが、あるString
クラスのテキストをに設定していることを意味しているよう"hello"
です。
関数ポインタを使用すると、Cでメソッドをエミュレートできます。
これはどのように達成されますか?
String
クラスが実際にstruct
シミュレートする方法への道として機能関数ポインタの束と。以下は、String
クラスの部分的な宣言です。
typedef struct String_Struct* String;
struct String_Struct
{
char* (*get)(const void* self);
void (*set)(const void* self, char* value);
int (*length)(const void* self);
};
char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);
String newString();
見て分かるように、String
クラスのメソッドは実際には宣言された関数への関数ポインターです。インスタンスを作成するにはString
、newString
関数は、それぞれの機能に関数ポインタを設定するために呼び出されます。
String newString()
{
String self = (String)malloc(sizeof(struct String_Struct));
self->get = &getString;
self->set = &setString;
self->length = &lengthString;
self->set(self, "");
return self;
}
たとえばgetString
、get
メソッドを呼び出すことによって呼び出される関数は、次のように定義されます。
char* getString(const void* self_obj)
{
return ((String)self_obj)->internal->value;
}
注目できることの1つは、オブジェクトのインスタンスの概念がなく、実際にはオブジェクトの一部であるメソッドがあるということです。そのため、呼び出しごとに「自己オブジェクト」を渡す必要があります。(そして、これは以前のコードリストから省略されたinternal
単なる隠しstruct
ファイルです-これは情報の非表示を実行する方法ですが、関数ポインターには関係ありません。)
したがって、s1->set("hello");
を実行するのではなく、でアクションを実行するためにオブジェクトを渡す必要がありますs1->set(s1, "hello")
。
ささいな説明として、自分自身への参照を渡さなければならないので、次のパートであるCでの継承に移ります。
我々はのサブクラスを作りたいとしましょうString
と言います、ImmutableString
。文字列の不変を作るために、set
この方法は、アクセス維持しながら、アクセスできませんget
としlength
、かつ受け入れるために、「コンストラクタ」を強制しますchar*
:
typedef struct ImmutableString_Struct* ImmutableString;
struct ImmutableString_Struct
{
String base;
char* (*get)(const void* self);
int (*length)(const void* self);
};
ImmutableString newImmutableString(const char* value);
基本的に、すべてのサブクラスで使用できるメソッドは、ここでも関数ポインターです。今回は、set
メソッドの宣言が存在しないため、で呼び出すことはできませんImmutableString
。
の実装に関してはImmutableString
、関連するコードは「コンストラクタ」関数であるnewImmutableString
:のみです。
ImmutableString newImmutableString(const char* value)
{
ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));
self->base = newString();
self->get = self->base->get;
self->length = self->base->length;
self->base->set(self->base, (char*)value);
return self;
}
インスタンスではImmutableString
、関数へのポインタget
とlength
方法は、実際に参照するString.get
とString.length
通過することにより、方法base
内部に格納されている可変String
オブジェクト。
関数ポインタを使用すると、スーパークラスからメソッドを継承できます。
さらに、Cの多態性について説明します。
たとえば、length
メソッドの動作を変更して、何らかの理由でクラス0
内で常に戻るImmutableString
ようにしたい場合は、次のことを行うだけです。
length
メソッドとして機能する関数を追加します。length
メソッドに設定します。でオーバーライドlength
メソッドを追加するにImmutableString
は、lengthOverrideMethod
:
int lengthOverrideMethod(const void* self)
{
return 0;
}
次に、length
コンストラクター内のメソッドの関数ポインターがにフックされますlengthOverrideMethod
。
ImmutableString newImmutableString(const char* value)
{
ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));
self->base = newString();
self->get = self->base->get;
self->length = &lengthOverrideMethod;
self->base->set(self->base, (char*)value);
return self;
}
これで、クラスlength
内のメソッドの動作がImmutableString
クラスと同じになるのではなくString
、length
メソッドはlengthOverrideMethod
関数で定義された動作を参照するようになります。
Cでオブジェクト指向プログラミングスタイルで書く方法をまだ学んでいるという免責事項を追加する必要があります。そのため、十分に説明しなかった、またはOOPを実装する最善の方法に関しては不適切な点がある可能性がありますしかし、私の目的は、関数ポインターの多くの使用法の1つを説明しようとすることでした。
Cでオブジェクト指向プログラミングを実行する方法の詳細については、次の質問を参照してください。
ClassName_methodName
関数の命名規則に従ってください。そうして初めて、C ++やPascalと同じランタイムとストレージのコストが得られます。
解雇ガイド:コードを手動でコンパイルして、x86マシンのGCCで関数ポインターを悪用する方法:
これらの文字列リテラルは、32ビットx86マシンコードのバイトです。 0xC3
あるのx86 ret
命令が。
通常はこれらを手動で書くのではなく、アセンブリ言語で記述してnasm
から、アセンブラーを使用してフラットバイナリにアセンブルし、Cの文字列リテラルに16進ダンプします。
EAXレジスタの現在の値を返します
int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
スワップ関数を書く
int a = 10, b = 20;
((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
forループカウンターを1000に書き込み、毎回いくつかの関数を呼び出します
((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
100まで数える再帰関数を書くこともできます
const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
i = ((int(*)())(lol))(lol);
コンパイラは、テキストセグメントの一部としてリンクされている.rodata
セクション(または.rdata
Windows)に文字列リテラルを配置します(関数のコードとともに)。
関数ポインタに文字列リテラルをキャストすることは必要とせずに動作しますので、テキストセグメントは、読む+ Execの権限を持っているmprotect()
か、VirtualProtect()
あなたが動的に割り当てられたメモリのために必要があると思いますのような呼び出しをシステム。(またはgcc -z execstack
、プログラムをスタック+データセグメント+ヒープ実行可能ファイルにすばやくハックとしてリンクします。)
これらを逆アセンブルするには、これをコンパイルしてバイトにラベルを付け、逆アセンブラを使用します。
// at global scope
const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";
を使用してコンパイルgcc -c -m32 foo.c
および逆アセンブルするとobjdump -D -rwC -Mintel
、アセンブリを取得できます。このコードは、EBX(呼び出し保存レジスタ)を破壊することによってABIに違反しており、一般に非効率的であることがわかります。
00000000 <swap>:
0: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4] # load int *a arg from the stack
4: 8b 5c 24 08 mov ebx,DWORD PTR [esp+0x8] # ebx = b
8: 8b 00 mov eax,DWORD PTR [eax] # dereference: eax = *a
a: 8b 1b mov ebx,DWORD PTR [ebx]
c: 31 c3 xor ebx,eax # pointless xor-swap
e: 31 d8 xor eax,ebx # instead of just storing with opposite registers
10: 31 c3 xor ebx,eax
12: 8b 4c 24 04 mov ecx,DWORD PTR [esp+0x4] # reload a from the stack
16: 89 01 mov DWORD PTR [ecx],eax # store to *a
18: 8b 4c 24 08 mov ecx,DWORD PTR [esp+0x8]
1c: 89 19 mov DWORD PTR [ecx],ebx
1e: c3 ret
not shown: the later bytes are ASCII text documentation
they're not executed by the CPU because the ret instruction sends execution back to the caller
このマシンコードは(おそらく)Windows、Linux、OS Xなどの32ビットコードで動作します。これらすべてのOSのデフォルトの呼び出し規約は、レジスタでより効率的にではなく、スタックで引数を渡します。ただし、EBXはすべての通常の呼び出し規約で呼び出し保存されているため、EBXを保存/復元せずにスクラッチレジスタとして使用すると、呼び出し元が簡単にクラッシュする可能性があります。
関数ポインタの私のお気に入りの用途の1つは、安くて簡単なイテレータです-
#include <stdio.h>
#define MAX_COLORS 256
typedef struct {
char* name;
int red;
int green;
int blue;
} Color;
Color Colors[MAX_COLORS];
void eachColor (void (*fp)(Color *c)) {
int i;
for (i=0; i<MAX_COLORS; i++)
(*fp)(&Colors[i]);
}
void printColor(Color* c) {
if (c->name)
printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}
int main() {
Colors[0].name="red";
Colors[0].red=255;
Colors[1].name="blue";
Colors[1].blue=255;
Colors[2].name="black";
eachColor(printColor);
}
int (*cb)(void *arg, ...)
。イテレータの戻り値により、(ゼロ以外の場合)早期に停止することもできます。
基本的な宣言子があれば、関数ポインターは簡単に宣言できます。
ID
:IDがAであります*D
:DポインタへD(<parameters>)
:D機能取っ<
パラメータを>
返しますDは、同じルールを使用して構築されたもう1つの宣言子です。最後に、どこかID
で、宣言されたエンティティの名前である(例については以下を参照)で終わります。何も取らずにintを返す関数へのポインタを取り、charを取り、intを返す関数へのポインタを返す関数を作成してみましょう。type-defsでは次のようになります
typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);
ご覧のとおり、typedefを使用して簡単に作成できます。typedefがなければ、上記の宣言子ルールを一貫して適用することも難しくありません。ご覧のように、ポインターが指す部分と、関数が返すものを逃しました。これは宣言の左端に表示されるものであり、重要ではありません。宣言がすでに作成されている場合は、最後に追加されます。そうしよう。一貫してそれを構築する、最初の言葉-とを使用[
して構造を示す]
:
function taking
[pointer to [function taking [void] returning [int]]]
returning
[pointer to [function taking [char] returning [int]]]
ご覧のとおり、宣言子を1つずつ追加することで、型を完全に記述することができます。構築には2つの方法があります。1つはボトムアップで、非常に適切なもの(葉)から始まり、識別子に至るまでの作業です。もう1つの方法は、トップダウンで、識別子から始まり、葉まで下がっていきます。両方の方法を紹介します。
構築は、右側のものから始まります。戻り値は、charを取る関数です。宣言子を明確に保つために、それらに番号を付けます。
D1(char);
簡単なため、charパラメータを直接挿入しました。で置き換えることD1
により、宣言子へのポインタを追加します*D2
。括弧を囲む必要があることに注意してください*D2
。これは、*-operator
および関数呼び出し演算子の優先順位を調べることで知ることができます()
。括弧がないと、コンパイラはそれをと読みます*(D2(char p))
。しかし*D2
、もちろんそれはもはやD1の単なる置き換えではありません。括弧は常に宣言子の前後で使用できます。したがって、実際にそれらを追加しすぎても何も問題はありません。
(*D2)(char);
戻り型が完成しました!それでは、代わっD2
関数宣言子のことで機能の撮影<parameters>
戻っている、D3(<parameters>)
私たちは今ではです。
(*D3(<parameters>))(char)
今回は関数宣言子になり、ポインター宣言子になりたく ないので、括弧は必要D3
ありません。あとは、パラメータだけです。パラメータは、戻り値の型とまったく同じように行われますが、でchar
置き換えられvoid
ます。だから私はそれをコピーします:
(*D3( (*ID1)(void)))(char)
そのパラメーターが完成したので、私はに置き換えましD2
たID1
(これはすでに関数へのポインターです-別の宣言子は必要ありません)。ID1
パラメータの名前になります。今、私は最後に上記で述べたように、すべての宣言子が変更する型を追加します-すべての宣言の左端に表示されるものです。関数の場合、それは戻り値の型になります。タイプを指すポインターの場合...タイプを書き留めると興味深いことに、それは逆の順序で右端に表示されます:)とにかく、それを置き換えると完全な宣言が得られます。int
もちろん両方とも。
int (*ID0(int (*ID1)(void)))(char)
ID0
その例では、関数の識別子を呼び出しました。
これは、型の説明の左端にある識別子から始まり、右に進むにつれてその宣言子をラップします。パラメータを返す関数から始めます<
>
ID0(<parameters>)
説明の次の部分(「戻る」の後)はへのポインタです。組み込みましょう:
*ID0(<parameters>)
次に、関数は、<
パラメータを>
返す関数を受け取りました。パラメータは単純なcharなので、本当に簡単なので、すぐにもう一度入れます。
(*ID0(<parameters>))(char)
我々は再びことを希望するので、我々は追加の括弧に注意してください*
最初に結合し、その後(char)
。それ以外の場合は、読んでいまし機能の撮影<
パラメータ>
機能を返すが...。いいえ、関数を返す関数は許可されていません。
次に、<
パラメーターを配置するだけです>
。派生の短いバージョンを表示します。これは、すでに方法を理解しているからです。
pointer to: *ID1
... function taking void returning: (*ID1)(void)
int
ボトムアップで行ったように、宣言子の前に置くだけで完了です
int (*ID0(int (*ID1)(void)))(char)
ボトムアップとトップダウンのどちらが良いですか?私はボトムアップに慣れていますが、一部の人々はトップダウンでより快適かもしれません。好みの問題だと思います。ちなみに、その宣言ですべての演算子を適用すると、intを取得することになります。
int v = (*ID0(some_function_pointer))(some_char);
これは、Cの宣言の優れた特性です。この宣言は、これらの演算子が識別子を使用する式で使用されている場合、左端の型を生成すると断言します。配列についても同様です。
この小さなチュートリアルが気に入っていただけたことを願っています!これで、関数の奇妙な宣言構文について不思議に思うとき、これにリンクできます。Cの内部をできるだけ少なくしようとしました。自由に編集/修正してください。
これらは、さまざまな時点でのさまざまな機能や、開発のさまざまなフェーズが必要な場合に非常に便利です。たとえば、コンソールを備えたホストコンピューターでアプリケーションを開発していますが、ソフトウェアの最終リリースはAvnet ZedBoardに配置されます(ディスプレイとコンソール用のポートがありますが、それらは必要とされません/必要ありません)最終リリース)。そのため、開発中はprintf
ステータスとエラーメッセージを表示するために使用しますが、完了したら、何も印刷したくありません。これが私がやったことです:
// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION
// Define which version we want to use
#define DEBUG_VERSION // The current version
// #define RELEASE_VERSION // To be uncommented when finished debugging
#ifndef __VERSION_H_ /* prevent circular inclusions */
#define __VERSION_H_ /* by using protection macros */
void board_init();
void noprintf(const char *c, ...); // mimic the printf prototype
#endif
// Mimics the printf function prototype. This is what I'll actually
// use to print stuff to the screen
void (* zprintf)(const char*, ...);
// If debug version, use printf
#ifdef DEBUG_VERSION
#include <stdio.h>
#endif
// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
#define INVALID_VERSION
#endif
#endif
// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
#define INVALID_VERSION
#endif
#endif
#ifdef INVALID_VERSION
// Won't allow compilation without a valid version define
#error "Invalid version definition"
#endif
にversion.c
存在する2つの関数プロトタイプを定義しますversion.h
#include "version.h"
/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return None
*
*****************************************************************************/
void board_init()
{
// Assign the print function to the correct function pointer
#ifdef DEBUG_VERSION
zprintf = &printf;
#else
// Defined below this function
zprintf = &noprintf;
#endif
}
/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
return;
}
関数ポインタが次のようにプロトタイプさversion.h
れていることに注意してください
void (* zprintf)(const char *, ...);
アプリケーションで参照されると、まだ定義されていない場所を指しているところから実行を開始します。
内version.c
で通知、board_init()
関数zprintf
で定義されているバージョンに応じて(関数シグネチャマッチ)固有の機能が割り当てられていますversion.h
zprintf = &printf;
zprintfはデバッグ目的でprintfを呼び出します
または
zprintf = &noprint;
zprintfは戻るだけで不要なコードを実行しません
コードを実行すると、次のようになります。
#include "version.h"
#include <stdlib.h>
int main()
{
// Must run board_init(), which assigns the function
// pointer to an actual function
board_init();
void *ptr = malloc(100); // Allocate 100 bytes of memory
// malloc returns NULL if unable to allocate the memory.
if (ptr == NULL)
{
zprintf("Unable to allocate memory\n");
return 1;
}
// Other things to do...
return 0;
}
上記のコードはprintf
、デバッグモードの場合は使用され、リリースモードの場合は何も実行されません。これは、プロジェクト全体を確認してコードをコメント化または削除するよりもはるかに簡単です。私がしなければならないのは、バージョンを変更するversion.h
ことだけで、コードが残りを行います!
関数ポインタは通常によって定義されtypedef
、パラメータと戻り値として使用されます。
上記の回答はすでに多くのことを説明していますが、私は完全な例を挙げます:
#include <stdio.h>
#define NUM_A 1
#define NUM_B 2
// define a function pointer type
typedef int (*two_num_operation)(int, int);
// an actual standalone function
static int sum(int a, int b) {
return a + b;
}
// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
return (*funp)(a, b);
}
// use function pointer as return value,
static two_num_operation get_sum_fun() {
return ∑
}
// test - use function pointer as variable,
void test_pointer_as_variable() {
// create a pointer to function,
two_num_operation sum_p = ∑
// call function via pointer
printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}
// test - use function pointer as param,
void test_pointer_as_param() {
printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}
// test - use function pointer as return value,
void test_pointer_as_return_value() {
printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}
int main() {
test_pointer_as_variable();
test_pointer_as_param();
test_pointer_as_return_value();
return 0;
}
Cの関数ポインターの大きな用途の1つは、実行時に選択された関数を呼び出すことです。たとえば、Cランタイムライブラリには2つのルーチンとがqsort
ありbsearch
、これらは、ソートされる2つの項目を比較するために呼び出される関数へのポインターを受け取ります。これにより、使用したい基準に基づいて、それぞれ何でもソートまたは検索できます。
非常に基本的な例です。呼び出される関数が1つあり、その関数print(int x, int y)
を呼び出す必要がある場合があります(同じタイプのadd()
またはのいずれかsub()
)、次に何をするか、print()
以下のように関数に1つの関数ポインター引数を追加します:
#include <stdio.h>
int add()
{
return (100+10);
}
int sub()
{
return (100-10);
}
void print(int x, int y, int (*func)())
{
printf("value is: %d\n", (x+y+(*func)()));
}
int main()
{
int x=100, y=200;
print(x,y,add);
print(x,y,sub);
return 0;
}
出力は次のとおりです。
値は:410
値は:390
最初から開始する機能には、実行を開始する場所からのメモリアドレスが含まれています アセンブリ言語では、これらは(「関数のメモリアドレス」を呼び出します)と呼ばれます。今度はCに戻ります。関数にメモリアドレスがある場合、Cのポインタによって操作できます。したがって、Cの規則によって
1.最初に、関数へのポインターを宣言する必要があります2.必要な関数のアドレスを渡します
****注->関数は同じタイプである必要があります****
このシンプルなプログラムはすべてのものを説明します。
#include<stdio.h>
void (*print)() ;//Declare a Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
//The Functions should Be of Same Type
int main()
{
print=sayhello;//Addressof sayhello is assigned to print
print();//print Does A call To The Function
return 0;
}
void sayhello()
{
printf("\n Hello World");
}
その後、32ビットアーキテクチャで上記のプログラムの機械語命令を機械が理解する方法を機械が理解する方法を見てみましょう。
赤いマーク領域は、アドレスがどのように交換され、eaxに格納されているかを示しています。それから彼らはeaxの呼び出し命令です。eaxには、関数の目的のアドレスが含まれています。
関数ポインタは、関数のアドレスを含む変数です。これはポインター変数ですが、いくつかの制限されたプロパティがあるため、データ構造内の他のポインター変数とほぼ同じように使用できます。
私が考えることができる唯一の例外は、関数ポインターを単一の値以外のものを指すものとして扱うことです。関数ポインタは1つのもの、つまり関数のエントリポイントを指すだけなので、関数ポインタをインクリメントまたはデクリメントしたり、オフセットを関数ポインタに加算/減算したりしてポインタ演算を行うことは、実際には役に立ちません。
関数ポインタ変数のサイズ、変数が占めるバイト数は、x32やx64など、基盤となるアーキテクチャによって異なります。
Cコンパイラが通常行うチェックの種類を実行するには、関数ポインタ変数の宣言で関数宣言と同じ種類の情報を指定する必要があります。関数ポインターの宣言/定義でパラメーターリストを指定しない場合、Cコンパイラーはパラメーターの使用をチェックできません。このチェックの欠如が役立つ場合がありますが、セーフティネットが削除されていることを覚えておいてください。
いくつかの例:
int func (int a, char *pStr); // declares a function
int (*pFunc)(int a, char *pStr); // declares or defines a function pointer
int (*pFunc2) (); // declares or defines a function pointer, no parameter list specified.
int (*pFunc3) (void); // declares or defines a function pointer, no arguments.
最初の2つの宣言は、次の点で多少似ています。
func
とる関数であるint
とchar *
し、ANを返すには、int
pFunc
int
とa char *
を受け取って返す関数のアドレスが割り当てられている関数ポインタですint
だから、上から、私たちは関数のアドレスがするソース行持つことができるfunc()
関数ポインタ変数に割り当てられているpFunc
のようにpFunc = func;
。
関数ポインターの宣言/定義で使用される構文に注意してください。括弧は、自然な演算子の優先順位の規則を克服するために使用されています。
int *pfunc(int a, char *pStr); // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr); // declares a function pointer that returns an int
いくつかの異なる使用例
関数ポインターの使用例:
int (*pFunc) (int a, char *pStr); // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr); // declare a pointer to a function pointer variable
struct { // declare a struct that contains a function pointer
int x22;
int (*pFunc)(int a, char *pStr);
} thing = {0, func}; // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr)); // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr)); // declare a function pointer that points to a function that has a function pointer as an argument
関数ポインターの定義では、可変長パラメーターリストを使用できます。
int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);
または、パラメータリストをまったく指定できません。これは便利ですが、Cコンパイラが提供された引数リストをチェックする機会がなくなります。
int sum (); // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int sum2(void); // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);
Cスタイルのキャスト
関数ポインタでCスタイルのキャストを使用できます。ただし、Cコンパイラーはチェックについてゆるい場合や、エラーではなく警告を表示する場合があることに注意してください。
int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum; // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum; // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum; // compiler error of bad cast generated, parenthesis are required.
関数ポインタと等価性の比較
関数ポインタが特定の関数アドレスと等しいことをif
ステートメントを使用して確認できますが、それがどれほど役立つかはわかりません。他の比較演算子の有用性はさらに低いようです。
static int func1(int a, int b) {
return a + b;
}
static int func2(int a, int b, char *c) {
return c[0] + a + b;
}
static int func3(int a, int b, char *x) {
return a + b;
}
static char *func4(int a, int b, char *c, int (*p)())
{
if (p == func1) {
p(a, b);
}
else if (p == func2) {
p(a, b, c); // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
} else if (p == func3) {
p(a, b, c);
}
return c;
}
関数ポインタの配列
そして、引数リストに違いがある各要素の関数ポインタの配列が必要な場合は、引数リストを指定せずvoid
に(つまり、引数がないことを意味するだけでなく)関数ポインタを次のように定義できます。 Cコンパイラからの警告が表示される場合があります。これは、関数への関数ポインターパラメーターでも機能します。
int(*p[])() = { // an array of function pointers
func1, func2, func3
};
int(**pp)(); // a pointer to a function pointer
p[0](a, b);
p[1](a, b, 0);
p[2](a, b); // oops, left off the last argument but it compiles anyway.
func4(a, b, 0, func1);
func4(a, b, 0, func2); // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);
// iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
func4(a, b, 0, p[i]);
}
// iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
(*pp)(a, b, 0); // pointer to a function pointer so must dereference it.
func4(a, b, 0, *pp); // pointer to a function pointer so must dereference it.
}
関数ポインターでnamespace
グローバルstruct
を使用するCスタイル
static
キーワードを使用して名前がファイルスコープである関数を指定し、namespace
C ++の機能に似たものを提供する方法としてこれをグローバル変数に割り当てることができます。
ヘッダーファイルで、名前空間になる構造体と、それを使用するグローバル変数を定義します。
typedef struct {
int (*func1) (int a, int b); // pointer to function that returns an int
char *(*func2) (int a, int b, char *c); // pointer to function that returns a pointer
} FuncThings;
extern const FuncThings FuncThingsGlobal;
次に、Cソースファイルで:
#include "header.h"
// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
return a + b;
}
static char *func2 (int a, int b, char *c)
{
c[0] = a % 100; c[1] = b % 50;
return c;
}
const FuncThings FuncThingsGlobal = {func1, func2};
これは、グローバル構造体変数の完全な名前とメンバー名を指定して、関数にアクセスするために使用されます。const
修飾子は、それが事故によって変更することができないというグローバルなように使用されています。
int abcd = FuncThingsGlobal.func1 (a, b);
関数ポインタの応用分野
DLLライブラリコンポーネントはnamespace
、特定のライブラリインターフェイスが、struct
含まれている関数ポインターの作成をサポートするライブラリインターフェイスのファクトリメソッドから要求されるCスタイルのアプローチに似た何かを実行できます。このライブラリインターフェイスは、要求されたDLLバージョンをロードし、作成します。必要な関数ポインタを含む構造体。次に、その構造体を要求元の呼び出し元に返して使用します。
typedef struct {
HMODULE hModule;
int (*Func1)();
int (*Func2)();
int(*Func3)(int a, int b);
} LibraryFuncStruct;
int LoadLibraryFunc LPCTSTR dllFileName, LibraryFuncStruct *pStruct)
{
int retStatus = 0; // default is an error detected
pStruct->hModule = LoadLibrary (dllFileName);
if (pStruct->hModule) {
pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
retStatus = 1;
}
return retStatus;
}
void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
if (pStruct->hModule) FreeLibrary (pStruct->hModule);
pStruct->hModule = 0;
}
これは次のように使用できます:
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
// ....
myLib.Func1();
// ....
FreeLibraryFunc (&myLib);
同じアプローチを使用して、基盤となるハードウェアの特定のモデルを使用するコードの抽象的なハードウェアレイヤーを定義できます。関数ポインタは、ハードウェア固有の関数でファクトリによって埋められ、抽象的なハードウェアモデルで指定された関数を実装するハードウェア固有の機能を提供します。これを使用して、特定のハードウェア関数インターフェイスを取得するためにファクトリ関数を呼び出すソフトウェアが使用する抽象的なハードウェアレイヤーを提供し、特定のターゲットに関する実装の詳細を知る必要なく、提供された関数ポインターを使用して、基盤となるハードウェアのアクションを実行できます。 。
デリゲート、ハンドラー、およびコールバックを作成する関数ポインター
一部のタスクまたは機能を委任する方法として、関数ポインターを使用できます。Cにおける古典的な例は、標準のCライブラリ関数で使用される比較デリゲート関数ポインタであるqsort()
とbsearch()
アイテムのリストをソートするか、アイテムのソートされたリスト上にバイナリサーチを行うための照合順序を提供します。比較関数デリゲートは、ソートまたはバイナリ検索で使用される照合アルゴリズムを指定します。
別の使用法は、C ++標準テンプレートライブラリコンテナーにアルゴリズムを適用することと似ています。
void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
unsigned char *pList = pArray;
unsigned char *pListEnd = pList + nItems * sizeItem;
for ( ; pList < pListEnd; pList += sizeItem) {
p (pList);
}
return pArray;
}
int pIncrement(int *pI) {
(*pI)++;
return 1;
}
void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
unsigned char *pList = pArray;
unsigned char *pListEnd = pList + nItems * sizeItem;
for (; pList < pListEnd; pList += sizeItem) {
p(pList, pResult);
}
return pArray;
}
int pSummation(int *pI, int *pSum) {
(*pSum) += *pI;
return 1;
}
// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;
ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);
別の例としては、GUIソースコードがあり、イベントが発生したときに実際に呼び出される関数ポインターを提供することにより、特定のイベントのハンドラーが登録されます。メッセージマップを備えたMicrosoft MFCフレームワークは、ウィンドウまたはスレッドに配信されるWindowsメッセージを処理するのと同様のものを使用します。
コールバックを必要とする非同期関数は、イベントハンドラーに似ています。非同期関数のユーザーは、非同期関数を呼び出してアクションを開始し、アクションが完了すると非同期関数が呼び出す関数ポインターを提供します。この場合のイベントは、タスクを完了する非同期関数です。
関数ポインターは型付きのコールバックであることが多いため、型保証のコールバックを確認することをお勧めします。コールバックではない関数のエントリーポイントなども同様です。
Cはかなり気まぐれで、同時に寛容です:)