Cの関数ポインタはどのように機能しますか?


1233

最近、Cで関数ポインタを使用した経験があります。

それで、あなた自身の質問に答えるという伝統を続けて、私はその主題に素早く飛び込む必要がある人々のために、非常に基本的なことの小さな要約を作ることにしました。


35
また、Cポインタの詳細な分析については、blogs.oracle.com / ksplice / entry / the_ksplice_pointer_challengeを参照してください。また、ゼロからプログラミングすると、マシンレベルでどのように機能するかがわかります。理解するCの「メモリモデルは、」 Cが作業をポインタ方法を理解するのに非常に有用です。
アバフェイ2013年

8
素晴らしい情報。しかし、タイトルによって、「関数ポインタがどのように機能するか」の説明が実際に表示されるのではなく、コードの記述方法がわかると思います:)
Bogdan Alexandru 14

回答:


1478

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;
}

19
素晴らしい情報をありがとう。関数ポインターがどこで使用されているか、または特に便利であるかについて洞察を追加できますか?
Rich.Carpenter、2009年

326
「functionPtr =&addInt;」"functionPtr = addInt;"と書くこともできます(多くの場合はそうです)。これは、このコンテキストの関数名は関数のアドレスに変換されると規格が定めているため、これも有効です。
フロブダル2009年

22
hlovdal、このコンテキストでは、これがfunctionPtr = ****************** addInt;の記述を可能にするものであることを説明するのは興味深いことです。
ヨハネスシャウブ-litb 2009年

105
@ Rich.Carpenter私はこれが4年では遅すぎることを知っていますが、他の人がこれから利益を得るかもしれないと思います関数ポインターは、パラメーターとして他の関数に関数を渡すのに役立ちます。奇妙な理由でその答えを見つけるのに私を探すのに多くの時間がかかりました。したがって、基本的には、C疑似ファーストクラスの機能を提供します。
giant91 2013年

22
@ Rich.Carpenter:関数ポインタは、ランタイムCPU検出に適しています。SSE、popcnt、AVXなどを利用するために、いくつかの関数の複数のバージョンを用意します。起動時に、関数ポインターを現在のCPUの各関数の最適なバージョンに設定します。他のコードでは、どこにでもCPU機能に条件付きブランチを用意するのではなく、関数ポインターを介して呼び出すだけです。次に、このCPUがをサポートしているとしても、それを適切に決定するための複雑なロジックを実行できます。pshufb低速なので、以前の実装はさらに高速です。x264 / x265はこれを広く使用しており、オープンソースです。
Peter Cordes 2015

304

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クラスのメソッドは実際には宣言された関数への関数ポインターです。インスタンスを作成するにはStringnewString関数は、それぞれの機能に関数ポインタを設定するために呼び出されます。

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

たとえばgetStringgetメソッドを呼び出すことによって呼び出される関数は、次のように定義されます。

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、関数へのポインタgetlength方法は、実際に参照するString.getString.length通過することにより、方法base内部に格納されている可変Stringオブジェクト。

関数ポインタを使用すると、スーパークラスからメソッドを継承できます。

さらに、Cの多態性について説明します。

たとえば、lengthメソッドの動作を変更して、何らかの理由でクラス0内で常に戻るImmutableStringようにしたい場合は、次のことを行うだけです。

  1. オーバーライドするlengthメソッドとして機能する関数を追加します。
  2. 「コンストラクター」に移動し、関数ポインターをオーバーライドする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クラスと同じになるのではなくStringlengthメソッドはlengthOverrideMethod関数で定義された動作を参照するようになります。

Cでオブジェクト指向プログラミングスタイルで書く方法をまだ学んでいるという免責事項を追加する必要があります。そのため、十分に説明しなかった、またはOOPを実装する最善の方法に関しては不適切な点がある可能性がありますしかし、私の目的は、関数ポインターの多くの使用法の1つを説明しようとすることでした。

Cでオブジェクト指向プログラミングを実行する方法の詳細については、次の質問を参照してください。


22
この答えは恐ろしいです!OOが何らかの形でドット表記に依存していることを意味するだけでなく、オブジェクトにジャンクを挿入することも奨励しています!
Alexei Averchenko 2012

27
これはオブジェクト指向で大丈夫ですが、Cスタイルのオブジェクト指向の近くにはありません。正しく実装されていないのは、JavaScriptスタイルのプロトタイプベースのオブジェクト指向です。C ++ / Pascalスタイルのオブジェクト指向オブジェクトを取得するには、次のことを行う必要があります。1 .仮想メンバーを持つ各クラスの仮想テーブルのconst構造体を用意します。2.ポリモーフィックオブジェクトでその構造体へのポインターを持っている。3.仮想テーブルを介して仮想メソッドを呼び出し、その他のすべてのメソッドを直接呼び出します。通常は、ClassName_methodName関数の命名規則に従ってください。そうして初めて、C ++やPascalと同じランタイムとストレージのコストが得られます。
モニカ

19
オブジェクト指向であることを意図していない言語でオブジェクト指向を操作することは常に悪い考えです。OOが必要でCがまだある場合は、C ++で動作します。
rbaleksandar 2013

20
@rbaleksandar Linuxカーネル開発者にそれを伝えてください。「常に悪い考え」はあなたの意見であり、私はこれに強く反対します。
Jonathon Reinhart、2015

6
私はこの答えは好きですが、mallocをキャストしないでください
cat

227

解雇ガイド:コードを手動でコンパイルして、x86マシンのGCCで関数ポインターを悪用する方法:

これらの文字列リテラルは、32ビットx86マシンコードのバイトです。 0xC3あるのx86 ret命令が

通常はこれらを手動で書くのではなく、アセンブリ言語で記述してnasmから、アセンブラーを使用してフラットバイナリにアセンブルし、Cの文字列リテラルに16進ダンプします。

  1. EAXレジスタの現在の値を返します

    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
  2. スワップ関数を書く

    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);
  3. 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
  4. 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セクション(または.rdataWindows)に文字列リテラルを配置します(関数のコードとともに)。

関数ポインタに文字列リテラルをキャストすることは必要とせずに動作しますので、テキストセグメントは、読む+ 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を保存/復元せずにスクラッチレジスタとして使用すると、呼び出し元が簡単にクラッシュする可能性があります。


8
注:C文字列は通常実行可能としてマークされていないため、データ実行防止が有効になっている場合(Windows XP SP2 +など)には機能しません。
SecurityMatt

5
こんにちはマット!最適化レベルによっては、GCCは文字列定数をTEXTセグメントにインライン化することが多いため、このタイプの最適化を許可しない限り、これは新しいバージョンのウィンドウでも機能します。(IIRC、2年以上前の私の投稿時のMINGWバージョンは、文字列リテラルをデフォルトの最適化レベルでインライン化します)
Lee

10
誰かがここで何が起こっているのか説明してもらえますか?それらの奇妙に見える文字列リテラルは何ですか?
ajay

56
@ajay彼は生の16進数値(たとえば、 '\ x00'は '/ 0'と同じで、どちらも0に等しい)を文字列に書き込んでいるように見え、文字列をC関数ポインターにキャストして実行します。彼は悪魔なので、C関数ポインター。
ejk314 14

3
こんにちはFUZxxlです。コンパイラとオペレーティングシステムのバージョンによって異なる場合があります。上記のコードはcodepad.orgで正常に動作するようです。codepad.org/FMSDQ3ME
Lee

115

関数ポインタの私のお気に入りの用途の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);
}

7
また、反復から何らかの出力を抽出したい場合は、ユーザー指定のデータへのポインターを渡す必要があります(クロージャーを考える)。
Alexei Averchenko 2012

1
同意した。イテレータはすべて次のようになりますint (*cb)(void *arg, ...)。イテレータの戻り値により、(ゼロ以外の場合)早期に停止することもできます。
Jonathon Reinhart、2015

24

基本的な宣言子があれば、関数ポインターは簡単に宣言できます。

  • ID: IDIDがAであります
  • ポインター:*DDポインタへ
  • 機能: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)

そのパラメーターが完成したので、私はに置き換えましD2ID1(これはすでに関数へのポインターです-別の宣言子は必要ありません)。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の内部をできるだけ少なくしようとしました。自由に編集/修正してください。


24

関数ポインタのもう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存在する2つの関数プロトタイプを定義します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;
}

関数ポインタが次のようにプロトタイプさversion.hれていることに注意してください

void (* zprintf)(const char *, ...);

アプリケーションで参照されると、まだ定義されていない場所を指しているところから実行を開始します。

version.cで通知、board_init()関数zprintfで定義されているバージョンに応じて(関数シグネチャマッチ)固有の機能が割り当てられていますversion.h

zprintf = &printf; zprintfはデバッグ目的でprintfを呼び出します

または

zprintf = &noprint; 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;
}

上記のコードはprintf、デバッグモードの場合は使用され、リリースモードの場合は何も実行されません。これは、プロジェクト全体を確認してコードをコメント化または削除するよりもはるかに簡単です。私がしなければならないのは、バージョンを変更するversion.hことだけで、コードが残りを行います!


4
Uは多くのパフォーマンス時間を失うことになります。代わりに、デバッグ/リリースに基づいてコードのセクションを有効または無効にするマクロを使用できます。
AlphaGoku 2018年

19

関数ポインタは通常によって定義され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 &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // 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;
}

14

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


10

最初から開始する機能には、実行を開始する場所からのメモリアドレスが含まれています アセンブリ言語では、これらは(「関数のメモリアドレス」を呼び出します)と呼ばれます。今度は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には、関数の目的のアドレスが含まれています。


8

関数ポインタは、関数のアドレスを含む変数です。これはポインター変数ですが、いくつかの制限されたプロパティがあるため、データ構造内の他のポインター変数とほぼ同じように使用できます。

私が考えることができる唯一の例外は、関数ポインターを単一の値以外のものを指すものとして扱うことです。関数ポインタは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とる関数であるintchar *し、ANを返すには、int
  • pFuncintと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キーワードを使用して名前がファイルスコープである関数を指定し、namespaceC ++の機能に似たものを提供する方法としてこれをグローバル変数に割り当てることができます。

ヘッダーファイルで、名前空間になる構造体と、それを使用するグローバル変数を定義します。

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メッセージを処理するのと同様のものを使用します。

コールバックを必要とする非同期関数は、イベントハンドラーに似ています。非同期関数のユーザーは、非同期関数を呼び出してアクションを開始し、アクションが完了すると非同期関数が呼び出す関数ポインターを提供します。この場合のイベントは、タスクを完了する非同期関数です。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.