質問
最近、Cで関数ポインタを使用した経験があります。
それで、あなた自身の質問に答えるという伝統に沿って、私は主題に素早く飛び込む必要がある人々のために、非常に基本的なことの小さな要約を作ることにしました。
解決
Cの関数ポインター
指す基本的な機能から始めましょう:
int addInt(int n, int m) {
return n+m;
}
まず、2つのint
を受け取り、typedef
を返す関数へのポインターを定義しましょう:
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 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;
}
他のヒント
Cの関数ポインターを使用して、Cでオブジェクト指向プログラミングを実行できます。
たとえば、次の行はCで記述されています。
String s1 = newString();
s1->set(s1, "hello");
はい、->
演算子とnew
演算子の欠如は無意味ですが、いくつかのString
クラスのテキストを"hello"
に設定していることを暗示しているようです。
関数ポインタを使用すると、 Cでメソッドをエミュレートできます。
これはどのように達成されますか?
struct
クラスは、実際にはメソッドをシミュレートする方法として機能する一連の関数ポインタを持つnewString
です。以下は、getString
クラスの部分的な宣言です。
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();
ご覧のとおり、get
クラスのメソッドは、実際には宣言された関数への関数ポインターです。 internal
のインスタンスを準備する際に、それぞれの関数への関数ポインターを設定するために、s1->set("hello");
関数が呼び出されます。
String newString()
{
String self = (String)malloc(sizeof(struct String_Struct));
self->get = &getString;
self->set = &setString;
self->length = &lengthString;
self->set(self, "");
return self;
}
たとえば、s1->set(s1, "hello")
メソッドを呼び出すことによって呼び出されるImmutableString
関数は、次のように定義されます。
char* getString(const void* self_obj)
{
return ((String)self_obj)->internal->value;
}
注目すべきことの1つは、オブジェクトのインスタンスという概念がなく、実際にはオブジェクトの一部であるメソッドがあるということです。したがって、<!> quot; self object <!> quot;呼び出しごとに渡す必要があります。 (およびset
は、以前のコードリストから省略された単なる非表示のlength
です。これは情報の非表示を実行する方法ですが、関数ポインターには関係ありません。)
したがって、char*
を実行できるのではなく、オブジェクトを渡してnewImmutableString
でアクションを実行する必要があります。
その簡単な説明は、自分への参照を邪魔にならないように渡さなければならないので、次の部分、 Cの継承に進みます。
String.get
のサブクラス、たとえばString.length
を作成したいとしましょう。文字列を不変にするために、base
および0
へのアクセスを維持しながらlengthOverrideMethod
メソッドにアクセスできず、<!> quot; constructor <!> quot;を強制します。 <=>:
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);
基本的に、すべてのサブクラスについて、使用可能なメソッドは再び関数ポインターです。今回は、<=>メソッドの宣言は存在しないため、<=>で呼び出すことはできません。
<=>の実装に関しては、関連するコードは<!> quot; constructor <!> quot;のみです。関数、<=>:
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;
}
<=>をインスタンス化する際、<=>および<=>メソッドへの関数ポインターは、内部的に格納されている<=>変数を介して、実際に<=>および<=>メソッドを参照します=>オブジェクト。
関数ポインタを使用すると、スーパークラスからメソッドを継承できます。
さらに Cのポリモーフィズムに進みます。
たとえば、何らかの理由で<=>クラスで常に<=>を返すように<=>メソッドの動作を変更する場合は、次のことを行う必要があります。
- オーバーライドする<=>メソッドとして機能する関数を追加します。
- <!> quot; constructor <!> quot;に移動します。関数ポインタをオーバーライドする<=>メソッドに設定します。
<=>にオーバーライドする<=>メソッドを追加するには、<=>:
を追加します。int lengthOverrideMethod(const void* self)
{
return 0;
}
次に、コンストラクターの<=>メソッドの関数ポインターが<=>:
に接続されます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;
}
今、<=>クラスの<=>メソッドと<=>クラスの動作が同じではなく、<=>メソッドが<=>関数で定義された動作を参照するようになりました。
Cでオブジェクト指向プログラミングスタイルを使用して記述する方法をまだ学習しているという免責事項を追加する必要があります。 CでOOPを実装しました。しかし、私の目的は、関数ポインターの多くの使用法の1つを説明することでした。
Cでオブジェクト指向プログラミングを実行する方法の詳細については、次の質問を参照してください。
解雇の手引き:コードを手動でコンパイルして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
セクション(またはWindowsでは.rdata
)セクションに配置します。これは、テキストセグメントの一部として(関数のコードと共に)リンクされます。
テキストセグメントには読み取り+実行許可があるため、文字列リテラルを関数ポインターにキャストすると、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);
}
基本的な宣言子を取得すると、関数ポインターの宣言が簡単になります。
- id:
ID
: IDはa - ポインター:
*D
: Dポインター - 関数:
D(<parameters>)
:<
パラメータ>
を返すD関数
Dは、同じルールを使用して作成された別の宣言子です。最後に、どこかで、[
(例については以下を参照)で終わります。これは、宣言されたエンティティの名前です。何も取らずにintを返す関数へのポインタを取り、charを取り、intを返す関数にポインタを返す関数を作成してみましょう。 type-defを使用すると、次のようになります
typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);
ご覧のとおり、typedefを使用して簡単に構築できます。 typedefがなければ、上記の宣言子規則が一貫して適用されても難しくありません。ご覧のように、ポインターが指す部分と、関数が返すものを見逃しました。これは、宣言の一番左に表示されるものであり、興味の対象ではありません。宣言子を既に作成している場合は、最後に追加されます。それをしましょう。一貫して構築します。最初は言葉遣い-]
とD1
を使用して構造を示します:
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パラメーターを直接挿入しました。 *D2
を*-operator
に置き換えて、宣言子へのポインターを追加します。 ()
を括弧で囲む必要があることに注意してください。これは、*(D2(char p))
および関数呼び出し演算子D2
の優先順位を調べることで知ることができます。括弧がなければ、コンパイラは<parameters>
として読み取ります。しかし、それはもちろん、D1のD3(<parameters>)
による単なる置き換えではありません。宣言子の前後に括弧が常に許可されます。したがって、実際に追加しすぎても何も問題はありません。
(*D2)(char);
戻り値の型が完成しました!ここで、D3
を、関数宣言子 char
を返す関数で置き換えます。これは、現在のvoid
です。
(*D3(<parameters>))(char)
今回は、ポインタ宣言子ではなく関数宣言子になりたい ため、括弧は必要ありません。素晴らしい、残っているのはそのためのパラメータだけです。パラメーターは、ID1
をint
に置き換えただけで、戻り値の型とまったく同じように実行されます。コピーします:
(*D3( (*ID1)(void)))(char)
ID0
を*
に置き換えました。これは、そのパラメーターで終了しているためです(既に関数へのポインターです-別の宣言子は不要です)。 (char)
はパラメーターの名前になります。さて、最後に、私はこれらのすべての宣言子が変更する型を追加します-すべての宣言の一番左に表示される型。関数の場合、それが戻り値の型になります。型などを指すポインターの場合...型を書き留めると面白いです。逆の順序で、右端に表示されます:)とにかく、それを置き換えると完全な宣言が得られます。もちろん、両方とも<=>です。
int (*ID0(int (*ID1)(void)))(char)
この例では、関数の識別子<=>を呼び出しました。
トップダウン
これは、型の説明の一番左にある識別子から始まり、右に進むにつれてその宣言子をラップします。 <=>パラメータを取得する関数<=>から戻る
ID0(<parameters>)
説明の次の要素(<!> quot; returning <!> quot;の後)は、 pointer to to です。組み込みましょう:
*ID0(<parameters>)
次は、関数が<=> parameters <=>を返す機能です。パラメータは単純な文字であるため、really trivial。
(*ID0(<parameters>))(char)
<=>を最初にバインドし、次にを<=>にバインドする必要があるため、追加した括弧に注意してください。それ以外の場合は、関数が<=> parameters <=>を返し、関数を返します... 。いいえ、関数を返す関数は許可されていません。
ここで<=> parameters <=>を入力するだけです。私はすでにあなたがすでにそれを行う方法のアイデアを持っていると思うので、派生の短いバージョンを示します。
pointer to: *ID1
... function taking void returning: (*ID1)(void)
ボトムアップで行ったように宣言子の前に<=>を置くだけで完了です
int v = (*ID0(some_function_pointer))(some_char);
素晴らしいこと
ボトムアップとトップダウンのどちらが良いですか?私はボトムアップに慣れていますが、トップダウンに慣れている人もいます。好みの問題だと思う。ちなみに、その宣言ですべての演算子を適用すると、intが返されます:
<*>これは、Cの宣言の優れたプロパティです。宣言は、これらの演算子が識別子を使用する式で使用される場合、一番左の型を生成することを表明します。配列についても同様です。
この小さなチュートリアルが気に入ったことを願っています!人々が関数の奇妙な宣言構文について疑問に思うとき、これにリンクできます。 Cの内部構造をできるだけ少なくしようとしました。その中のものを自由に編集/修正してください。
関数ポインターのもう1つの良い使用法:
簡単にバージョンを切り替える
これらは、さまざまな時期や開発のさまざまな段階でさまざまな機能が必要な場合に使用すると非常に便利です。たとえば、コンソールを備えたホストコンピューターでアプリケーションを開発していますが、ソフトウェアの最終リリースはAvnet ZedBoardに置かれます(ディスプレイとコンソール用のポートがありますが、最終リリース)。そのため、開発中にステータスとエラーメッセージを表示するためにprintf
を使用しますが、完了したら、何も印刷したくありません。これが私がやったことです:
version.h
// 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
では、version.h
version.c
#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;
}
void (* zprintf)(const char *, ...);
で関数ポインターがどのようにプロトタイプ化されるかに注意してください
board_init()
アプリケーションで参照されると、関数ポインターが開始されますまだ定義されていない場所を指している場所で実行します。
zprintf
で、zprintf = &printf;
<=で定義されているバージョンに応じて、zprintf = &noprint;
が<=>に一意の関数(関数シグネチャが一致する)が割り当てられていることに注意してください。 > zprintfはデバッグ目的でprintfを呼び出します
または
<=> zprintfは単に戻り、不要なコードを実行しません
コードの実行は次のようになります。
mainProg.c
#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;
}
上記のコードは、デバッグモードの場合は<=>を使用し、リリースモードの場合は何も行いません。これは、プロジェクト全体を調べてコードをコメントアウトまたは削除するよりもはるかに簡単です。私がする必要があるのは、<=>のバージョンを変更するだけです。残りはコードで処理されます!
関数ポインタは通常typedef
で定義され、param <!> ampとして使用されます。戻り値。
上記の回答はすでに多くの説明がありましたが、完全な例を示します。
#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ランタイムライブラリには、 qsort
という2つのルーチンがあります。および bsearch
は、呼び出される関数へのポインターを取得しますソートされている2つのアイテムを比較します。これにより、使用したい基準に基づいて、あらゆるものをソートまたは検索できます。
非常に基本的な例、print(int x, int y)
と呼ばれる1つの関数があり、その関数が同じタイプの関数(add()
またはsub()
のいずれか)を呼び出す必要がある場合、次に何をするか、以下に示すように、1つの関数ポインター引数をprint()
関数に追加します。
#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
関数ポインタは、関数のアドレスを含む変数です。いくつかの制限されたプロパティを持つポインター変数なので、データ構造の他のポインター変数とほとんど同じように使用できます。
私が考えることができる唯一の例外は、関数ポインタが単一の値以外を指すものとして扱うことです。関数ポインタは関数のエントリポイントを指すだけなので、関数ポインタのインクリメントまたはデクリメント、または関数ポインタへのオフセットの加算/減算によるポインタ演算は、実際には役に立ちません。
関数ポインタ変数のサイズ、変数が占有するバイト数は、基礎となるアーキテクチャによって異なります。 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 *
を取り、pFunc
を返す関数です
-
func()
は、pFunc = func;
およびif
を取り、void
を返す関数のアドレスが割り当てられる関数ポインターです。
上記から、namespace
のように、関数のアドレスが関数ポインタ変数struct
に割り当てられるソース行を作成できます。
自然な演算子の優先順位規則を克服するために括弧が使用される関数ポインター宣言/定義で使用される構文に注意してください。
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.
関数ポインタの同等性の比較
static
ステートメントを使用して、関数ポインターが特定の関数アドレスに等しいことを確認できますが、それがどれほど役立つかはわかりません。他の比較演算子はさらに有用性が低いようです。
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;
}
関数ポインタの配列
そして、引数リストに違いがある各要素の関数ポインターの配列が必要な場合は、引数リストを指定せずに関数ポインターを定義できます(const
ではなく、引数が指定されていないだけです)次のようなものですが、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.
}
Cスタイルqsort()
関数ポインターでグローバルbsearch()
を使用
<=>キーワードを使用して、ファイルスコープという名前の関数を指定し、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};
これは、グローバル構造変数の完全な名前と関数にアクセスするメンバー名を指定することで使用されます。 <=>修飾子はグローバルで使用されるため、誤って変更することはできません。
int abcd = FuncThingsGlobal.func1 (a, b);
関数ポインターの適用領域
DLLライブラリコンポーネントは、特定のライブラリインターフェイスが、関数ポインタを含む<=>の作成をサポートするライブラリインターフェイスのファクトリメソッドから要求される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ライブラリ関数<=>および<=>で使用される比較デリゲート関数ポインターで、アイテムのリストを並べ替えたり、並べ替えられたアイテムのリストに対してバイナリ検索を実行するための照合順序を提供します。比較関数デリゲートは、並べ替えまたはバイナリ検索で使用される照合アルゴリズムを指定します。
別の使用法は、アルゴリズムを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);
もう1つの例は、特定のイベントのハンドラーが、イベントが発生したときに実際に呼び出される関数ポインターを提供することによって登録されるGUIソースコードです。メッセージマップを備えたMicrosoft MFCフレームワークは、ウィンドウまたはスレッドに配信されるWindowsメッセージの処理に類似したものを使用します。
コールバックを必要とする非同期関数は、イベントハンドラーに似ています。非同期関数のユーザーは、非同期関数を呼び出してアクションを開始し、アクションが完了すると非同期関数が呼び出す関数ポインターを提供します。この場合、イベントはタスクを完了する非同期関数です。
ゼロから開始する機能には、実行を開始する場所からのメモリアドレスがあります。アセンブリ言語では、これらは(<<> quot;関数のメモリアドレス<!> quot;を呼び出します)として呼び出されます。今度はCに戻ります。関数にメモリアドレスがある場合、Cのポインタで操作できます。 C
1。まず、関数へのポインタを宣言する必要があります 2.目的の関数のアドレスを渡す
****注-<!> gt;関数は同じ型である必要があります****
このシンプルなプログラムは、すべてを説明します。
#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");
}
その後、上記のプログラムの機械命令のGlimpseを機械が理解する方法を参照してください。 32ビットアーキテクチャ。
赤いマーク領域は、アドレスの交換方法とeaxでの保存方法を示しています。次に、eaxの呼び出し命令があります。 eaxには、関数の目的のアドレスが含まれています。
関数ポインターはしばしば型付きコールバックであるため、型セーフコールバックをご覧ください。 。コールバックではない関数のエントリポイントなどにも同じことが当てはまります。
Cは非常に気まぐれで、同時に寛容です:)