私はちょうど興味深い質問を読んだばかりです here それはさらに2つのことについて疑問に思います:
void *
へのポインタのように表示されますか、またはより多くの情報(戻り値の型、引数の数、引数の型など)を保持しますか?なぜ誰かがポインターを比較するのですか?次のシナリオを検討してください-
関数ポインタの配列があり、それがコールバックチェーンであり、それらのそれぞれを呼び出す必要があると言います。リストは、NULL
(またはセンチネル)関数ポインターで終了します。リストの最後に到達したかどうかは、このセンチネルポインターと比較して比較する必要があります。また、この場合は、以前のOPが、類似していても関数ごとに異なるポインターを使用する必要があるという懸念を正当化します。
コンパイラはそれらを異なって見ますか?はい。型情報には、引数と戻り値の型に関するすべての情報が含まれます。
たとえば、次のコードはコンパイラによって拒否されるべきです。
void foo(int a);
void (*bar)(long) = foo; // Without an explicit cast
なぜ誰もが関数ポインターを比較する必要があるのですか?次に例を示します。
#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では、関数ポインターはプロトタイプなしで宣言できますが、これは廃止された機能です。
- 概念上、関数の一意性が異なる名前で保証されていることを考えると、なぜ誰かが関数ポインタを比較する必要があるのでしょうか?
関数ポインターは、プログラム内の異なる時点で異なる関数を指すことができます。
次のような変数がある場合
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
}
- コンパイラーは関数ポインターを特別なポインターと見なしますか?つまり、それらはvoid *へのポインタのように見えますか、それともより豊富な情報(戻り値の型、引数の数、引数の型など)を保持していますか?
はい、関数ポインターは特別なポインターであり、オブジェクトを指すポインターとは異なります。
関数ポインターのタイプには、コンパイル時にすべての情報が含まれます。したがって、関数ポインターを与えると、コンパイラーはすべての情報(戻り値の型、引数の数、およびその型)を取得します。
関数ポインターに関する古典的な部分は、他の回答ですでに説明されています。
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;
}
私があなたにそうするようにアドバイスしたふりをしないでください!現在では廃止された機能と見なされているため、使用しないでください。私はそれに対してあなたに警告しています。私見では、いくつかの例外的に受け入れられるユースケースしかありませんでした。
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つの関数ポインターに互換性がありません。
WNDCLASS
?のような機能を実装する方法を想像してください。
ウィンドウクラスを相互に区別するためのlpszClassName
がありますが、異なるクラスを相互に区別するために使用できる文字列を必要としない(または持たない)としましょう。
あなたがdo持っているのは、ウィンドウクラス手続きlpfnWndProc
(タイプ WindowProc
)です。
だから今誰かが同じRegisterClass
で lpfnWndProc
を2回呼び出したらどうしますか?
同じクラスの再登録を何らかの方法でdetectしてエラーを返す必要があります。
これは、コールバック関数を比較することが論理的に必要な場合の1つです。
関数ポインターは変数です。概念上、変数の一意性が異なる名前で保証されているのに、なぜ変数を比較する必要があるのでしょうか?まあ、2つの変数が同じ値を持つことがあり、それが当てはまるかどうかを知りたい場合があります。
Cは、同じ引数リストと戻り値を持つ関数へのポインタは同じ型であると見なします。