web-dev-qa-db-ja.com

Cの関数ポインター-性質と使用法

私はちょうど興味深い質問を読んだばかりです here それはさらに2つのことについて疑問に思います:

  1. 概念上、関数の一意性が異なる名前で保証されていることを考えると、なぜ誰かが関数ポインタを比較する必要があるのでしょうか?
  2. コンパイラーは関数ポインターを特別なポインターと見なしますか?つまり、void *へのポインタのように表示されますか、またはより多くの情報(戻り値の型、引数の数、引数の型など)を保持しますか?
32
  1. なぜ誰かがポインターを比較するのですか?次のシナリオを検討してください-

    関数ポインタの配列があり、それがコールバックチェーンであり、それらのそれぞれを呼び出す必要があると言います。リストは、NULL(またはセンチネル)関数ポインターで終了します。リストの最後に到達したかどうかは、このセンチネルポインターと比較して比較する必要があります。また、この場合は、以前のOPが、類似していても関数ごとに異なるポインターを使用する必要があるという懸念を正当化します。

  2. コンパイラはそれらを異なって見ますか?はい。型情報には、引数と戻り値の型に関するすべての情報が含まれます。

    たとえば、次のコードはコンパイラによって拒否されるべきです。

    void foo(int a);
    void (*bar)(long) = foo; // Without an explicit cast
    
20

なぜ誰もが関数ポインターを比較する必要があるのですか?次に例を示します。

#include <stdbool.h>

/*
 * Register a function to be executed on event. A function may only be registered once.
 * Input:
 *   arg - function pointer
 * Returns:
 *   true on successful registration, false if the function is already registered.
 */
bool register_function_for_event(void (*arg)(void));

/*
 * Un-register a function previously registered for execution on event.
 * Input:
 *   arg - function pointer
 * Returns:
 *   true on successful un-registration, false if the function was not registered.
 */
bool unregister_function_for_event(void (*arg)(void));

register_function_for_eventの本文には、argのみが表示されます。関数名は表示されません。誰かが同じ関数を2回登録していることを報告するには、関数ポインターを比較する必要があります。

上記を補完するためにunregister_function_for_eventのようなものをサポートしたい場合、唯一の情報は関数アドレスです。したがって、削除を許可するには、再度渡す必要があります。

より豊富な情報については、はい。関数型にプロトタイプが含まれている場合、それは静的型情報の一部です。 Cでは、関数ポインターはプロトタイプなしで宣言できますが、これは廃止された機能です。

37
StoryTeller
  1. 概念上、関数の一意性が異なる名前で保証されていることを考えると、なぜ誰かが関数ポインタを比較する必要があるのでしょうか?

関数ポインターは、プログラム内の異なる時点で異なる関数を指すことができます。

次のような変数がある場合

void (*fptr)(int);

入力としてintを受け入れ、voidを返す任意の関数を指すことができます。

あなたが持っているとしましょう:

void function1(int)
{
}

void function2(int)
{
}

次を使用できます。

fptr = function1;
foo(fptr);

または:

fptr = function2;
foo(fptr);

fooが1つの関数を指しているか別の関数を指しているかに応じて、fptrでさまざまな処理を実行できます。したがって、次の必要性:

if ( fptr == function1 )
{
    // Do stuff 1
}
else
{
    // Do stuff 2
}
  1. コンパイラーは関数ポインターを特別なポインターと見なしますか?つまり、それらはvoid *へのポインタのように見えますか、それともより豊富な情報(戻り値の型、引数の数、引数の型など)を保持していますか?

はい、関数ポインターは特別なポインターであり、オブジェクトを指すポインターとは異なります。

関数ポインターのタイプには、コンパイル時にすべての情報が含まれます。したがって、関数ポインターを与えると、コンパイラーはすべての情報(戻り値の型、引数の数、およびその型)を取得します。

19
R Sahu

関数ポインターに関する古典的な部分は、他の回答ですでに説明されています。

  • 他のポインターと同様に、関数へのポインターは異なる時点で異なるオブジェクトを指すことができるため、それらを比較することは意味があります。
  • 関数へのポインターは特別であり、他のポインタータイプ(void *そしてC言語でも)。
  • リッチ部分(関数シグネチャ)は関数タイプに格納されます-上記の文の理由。

ただし、Cには(レガシー)関数宣言モードがあります。戻り値の型とすべてのパラメーターの型を宣言する完全なプロトタイプモードに加えて、Cはいわゆるparameter listモードを使用できます。 K&Rモード。このモードでは、宣言は戻り型のみを宣言します。

int (*fptr)();

Cでは、intおよび任意のパラメーターを受け入れるを返す関数へのポインターを宣言します。間違ったパラメーターリストで使用することは、単に未定義の動作(UB)になります。

したがって、これは正当なCコードです。

#include <stdio.h>
#include <string.h>

int add2(int a, int b) {
    return a + b;
}
int add3(int a, int b, int c) {
    return a + b + c;
}

int(*fptr)();
int main() {
    fptr = add2;
    printf("%d\n", fptr(1, 2));
    fptr = add3;
    printf("%d\n", fptr(1, 2, 3));
    /* fprintf("%d\n", fptr(1, 2)); Would be UB */
    return 0;
}

私があなたにそうするようにアドバイスしたふりをしないでください!現在では廃止された機能と見なされているため、使用しないでください。私はそれに対してあなたに警告しています。私見では、いくつかの例外的に受け入れられるユースケースしかありませんでした。

5
Serge Ballesta

1)多くの状況があります。有限状態マシンの典型的な実装を例にとります。

typedef void state_func_t (void);

const state_func_t* STATE_MACHINE[] =
{
  state_init,
  state_something,
  state_something_else
};

...

for(;;)
{
  STATE_MACHINE[state]();
}

特定の状況のた​​めに、呼び出し元にいくつかの追加コードを含める必要がある場合があります。

if(STATE_MACHINE[state] == state_something)
{
  print_debug_stuff();
}

2)はい、Cコンパイラーはそれらを特殊タイプと見なします。実際、関数ポインタは、オブジェクト型へのポインタとは異なり、void*との間で暗黙的に変換できないため、他のC型よりも厳密な型安全性を備えています。 (C11 6.3.2.3/1)。また、void*との間で明示的にキャストすることもできません。そうすると、非標準の拡張機能が呼び出されます。

関数ポインタ内のすべては、その型を決定するために重要です:戻り値の型、パラメータの型、およびパラメータの数。これらはすべて一致する必要があります。一致しない場合、2つの関数ポインターに互換性がありません。

2
Lundin

WNDCLASS ?のような機能を実装する方法を想像してください。

ウィンドウクラスを相互に区別するためのlpszClassNameがありますが、異なるクラスを相互に区別するために使用できる文字列を必要としない(または持たない)としましょう。

あなたがdo持っているのは、ウィンドウクラス手続きlpfnWndProc(タイプ WindowProc )です。

だから今誰かが同じRegisterClasslpfnWndProc を2回呼び出したらどうしますか?
同じクラスの再登録を何らかの方法でdetectしてエラーを返す必要があります。

これは、コールバック関数を比較することが論理的に必要な場合の1つです。

2
Mehrdad

関数ポインターは変数です。概念上、変数の一意性が異なる名前で保証されているのに、なぜ変数を比較する必要があるのでしょうか?まあ、2つの変数が同じ値を持つことがあり、それが当てはまるかどうかを知りたい場合があります。

Cは、同じ引数リストと戻り値を持つ関数へのポインタは同じ型であると見なします。

1