引数を持つ関数へのポインターの typedef を持っている他の人々のコードを読んだとき、私はいつも少し困惑していました。少し前に C で書かれた数値アルゴリズムを理解しようとしていたときに、そのような定義にたどり着くのに時間がかかったのを思い出します。では、関数へのポインター用の適切な typedef を作成する方法 (すべきこととすべきでないこと) に関するヒントと考えを共有していただけますか? それらが役立つ理由と、他の人の作業を理解する方法について教えてください。ありがとう!
7 に答える
signal()
C 標準の関数を考えてみましょう。
extern void (*signal(int, void(*)(int)))(int);
完全にあいまいです-これは、整数と、整数を引数として取り、何も返さない関数へのポインターの2つの引数をとる関数であり、( signal()
) 整数を引数として取り、返す関数へのポインターを返しますなし。
あなたが書く場合:
typedef void (*SignalHandler)(int signum);
次に、代わりに次のように宣言できますsignal()
。
extern SignalHandler signal(int signum, SignalHandler handler);
これは同じことを意味しますが、通常はやや読みやすいと見なされます。関数が と を取り、 を返すことはint
明らかSignalHandler
ですSignalHandler
。
ただし、少し慣れが必要です。ただし、できないことの 1 つはSignalHandler
typedef
、関数定義で を使用してシグナル ハンドラ関数を作成することです。
私はまだ関数ポインターを次のように呼び出すことを好む古い学校です。
(*functionpointer)(arg1, arg2, ...);
最新の構文は次のとおりです。
functionpointer(arg1, arg2, ...);
なぜそれが機能するのかがわかりますfunctionpointer
..
サムは次のようにコメントしています。
この説明は以前にも見たことがあります。そして、今の場合のように、私が得られなかったのは2つのステートメント間の関係だったと思います:
extern void (*signal(int, void()(int)))(int); /*and*/ typedef void (*SignalHandler)(int signum); extern SignalHandler signal(int signum, SignalHandler handler);
または、私が聞きたいのは、あなたが持っている 2 番目のバージョンを思いつくために使用できる基本的な概念は何ですか? "SignalHandler" と最初の typedef を接続する基本は何ですか? ここで説明する必要があるのは、typedef が実際にここで行っていることだと思います。
もう一度試してみましょう。これらの最初のものは C 標準から直接持ち上げられています - 私はそれを再入力し、括弧が正しいことを確認しました (修正するまではそうではありませんでした - 覚えるのが難しいクッキーです)。
まず、typedef
が型のエイリアスを導入していることを思い出してください。したがって、エイリアスはSignalHandler
であり、そのタイプは次のとおりです。
引数として整数を取り、何も返さない関数へのポインタ。
「何も返さない」部分は綴られていvoid
ます。整数である引数は(私が信頼する)自明です。次の表記は、指定された引数を取り、指定された型を返す関数へのポインターを C がどのように綴るかを単純に (またはそうではなく) 示しています。
type (*function)(argtypes);
シグナル ハンドラーの型を作成したら、それを使用して変数などを宣言できます。例えば:
static void alarm_catcher(int signum)
{
fprintf(stderr, "%s() called (%d)\n", __func__, signum);
}
static void signal_catcher(int signum)
{
fprintf(stderr, "%s() called (%d) - exiting\n", __func__, signum);
exit(1);
}
static struct Handlers
{
int signum;
SignalHandler handler;
} handler[] =
{
{ SIGALRM, alarm_catcher },
{ SIGINT, signal_catcher },
{ SIGQUIT, signal_catcher },
};
int main(void)
{
size_t num_handlers = sizeof(handler) / sizeof(handler[0]);
size_t i;
for (i = 0; i < num_handlers; i++)
{
SignalHandler old_handler = signal(handler[i].signum, SIG_IGN);
if (old_handler != SIG_IGN)
old_handler = signal(handler[i].signum, handler[i].handler);
assert(old_handler == SIG_IGN);
}
...continue with ordinary processing...
return(EXIT_SUCCESS);
}
シグナルハンドラでの使用を避ける方法に注意してください。printf()
では、コードをきれいにコンパイルするために必要な 4 つの標準ヘッダーを省略したことを除けば、ここで何をしたのでしょうか。
最初の 2 つの関数は、単一の整数を取り、何も返さない関数です。それらの 1 つは実際には のおかげでまったく返されませんexit(1);
が、もう 1 つはメッセージを出力した後に返されます。C 標準では、シグナル ハンドラ内で多くのことを行うことは許可されていないことに注意してください。POSIXは許可されていることに関してもう少し寛大ですが、公式には call を認可していませんfprintf()
。受信したシグナル番号も出力します。alarm_handler()
関数では、値は常にそれSIGALRM
がハンドラーである唯一のシグナルですが、同じ関数が両方に使用されるため、またはシグナル番号としてsignal_handler()
取得される場合があります。SIGINT
SIGQUIT
次に、構造体の配列を作成します。各要素は、シグナル番号とそのシグナル用にインストールされるハンドラーを識別します。私は 3 つのシグナルについて心配することにしました。私はしばしば 、 、およびそれらが定義されているかどうか (条件付きコンパイル) について心配してSIGHUP
いSIGPIPE
ましSIGTERM
た#ifdef
が、それは物事を複雑にするだけです。sigaction()
おそらくの代わりにPOSIX を使用することもありますsignal()
が、それは別の問題です。私たちが始めたことに固執しましょう。
このmain()
関数は、インストールされるハンドラーのリストを反復処理します。ハンドラーごとに、プロセスが現在シグナルを無視しているかどうかを確認するために最初に呼び出し、その間にシグナルが無視されたままになるようにハンドラーとしてsignal()
インストールします。SIG_IGN
シグナルが以前に無視されていなかった場合は、signal()
再度呼び出して、今度は優先シグナル ハンドラーをインストールします。(他の値はおそらく SIG_DFL
、シグナルのデフォルトのシグナルハンドラです。) 「signal()」への最初の呼び出しでハンドラが設定されSIG_IGN
、signal()
前のエラーハンドラが返されるため、ステートメントのold
後の値は、 - したがってアサーションif
である必要があります。SIG_IGN
(まあ、そうかもしれないSIG_ERR
何かが劇的にうまくいかなかった場合-しかし、アサートの発火からそれについて学ぶでしょう.)
その後、プログラムはその処理を実行し、正常に終了します。
関数の名前は、適切な型の関数へのポインタと見なすことができることに注意してください。たとえば、イニシャライザのように、関数呼び出しの括弧を適用しない場合、関数名は関数ポインタになります。pointertofunction(arg1, arg2)
これは、表記法を介して関数を呼び出すことが合理的である理由でもあります。が表示されている場合、それは関数へのポインターであり、したがって関数ポインターを介した関数の呼び出しであるalarm_handler(1)
と考えることができます。alarm_handler
alarm_handler(1)
これまでのところ、SignalHandler
適切な型の値を割り当てることができる限り、変数は比較的簡単に使用できることを示してきました。これは、2 つのシグナル ハンドラー関数が提供するものです。
ここで、質問に戻ります - の 2 つの宣言が互いにどのようにsignal()
関係しているかです。
2 番目の宣言を確認しましょう。
extern SignalHandler signal(int signum, SignalHandler handler);
関数名と型を次のように変更した場合:
extern double function(int num1, double num2);
int
これを引数として anと aを取り、値double
を返す関数として解釈しても問題はありませんdouble
(そうでしょうか? それが問題である場合は、大騒ぎしないほうがよいでしょう - しかし、難しい質問をすることには注意する必要があります)。問題がある場合はこれとして)。
ここでdouble
、signal()
関数は aではなく、aSignalHandler
を 2 番目の引数として取り、結果として 1 を返します。
それを次のように扱うこともできるメカニズム:
extern void (*signal(int signum, void(*handler)(int signum)))(int signum);
説明するのは難しいので、おそらく台無しにします。今回はパラメーターに名前を付けましたが、名前は重要ではありません。
一般に、C の宣言メカニズムは次のようになります。
type var;
あなたが書くとき、var
それは与えられた の値を表しますtype
。例えば:
int i; // i is an int
int *ip; // *ip is an int, so ip is a pointer to an integer
int abs(int val); // abs(-1) is an int, so abs is a (pointer to a)
// function returning an int and taking an int argument
標準でtypedef
は、 は文法上のストレージ クラスとして扱われ、むしろstatic
とextern
がストレージ クラスです。
typedef void (*SignalHandler)(int signum);
つまり、次のようにSignalHandler
呼び出される型 (alarm_handler など) の変数が表示される場合:
(*alarm_handler)(-1);
結果はありますtype void
- 結果はありません。And(*alarm_handler)(-1);
はalarm_handler()
with argumentの呼び出しです-1
。
したがって、次のように宣言したとします。
extern SignalHandler alt_signal(void);
だということだ:
(*alt_signal)();
空値を表します。したがって:
extern void (*alt_signal(void))(int signum);
同等です。これは、 aを返すだけでなく、 int と a の両方を引数として受け入れるsignal()
ため、より複雑になります。SignalHandler
SignalHandler
extern void (*signal(int signum, SignalHandler handler))(int signum);
extern void (*signal(int signum, void (*handler)(int signum)))(int signum);
それでも混乱する場合は、どうすればよいかわかりません。まだいくつかのレベルで謎に包まれていますが、私はそれがどのように機能するかに慣れてきたので、さらに25年間それを使い続ければ、それを伝えることができます.またはそう、それはあなたにとって第 2 の性質になります (そして、あなたが賢い場合は、おそらくもう少し速くなります)。
関数ポインターは他のポインターと似ていますが、データのアドレス(ヒープまたはスタック上)ではなく、関数のアドレスを指します。他のポインタと同様に、正しく入力する必要があります。関数は、戻り値と受け入れるパラメーターのタイプによって定義されます。したがって、関数を完全に説明するには、その戻り値を含める必要があり、各パラメーターのタイプはacceptsです。そのような定義をtypedefするときは、その定義を使用してポインターを作成および参照するのを容易にする「わかりやすい名前」を付けます。
したがって、たとえば、関数があると仮定します。
float doMultiplication (float num1, float num2 ) {
return num1 * num2; }
次に、次のtypedef:
typedef float(*pt2Func)(float, float);
doMulitplication
この関数を指すために使用できます。これは、floatを返し、それぞれがfloat型の2つのパラメーターを受け取る関数へのポインターを定義するだけです。この定義にはわかりやすい名前が付いていpt2Func
ます。pt2Func
floatを返し、2つのfloatを取り込む任意の関数を指すことができることに注意してください。
したがって、次のようにdoMultiplication関数を指すポインターを作成できます。
pt2Func *myFnPtr = &doMultiplication;
次のように、このポインタを使用して関数を呼び出すことができます。
float result = (*myFnPtr)(2.0, 5.1);
これは良い読み物になります:http://www.newty.de/fpt/index.html
cdecl
関数ポインタ宣言のような奇妙な構文を解読するための優れたツールです。それらを生成するためにも使用できます。
複雑な宣言を将来のメンテナンス(自分自身または他の人による)のために解析しやすくするためのヒントに関してtypedef
は、小さなチャンクを作成し、それらの小さな部分をより大きくより複雑な式の構成要素として使用することをお勧めします。例えば:
typedef int (*FUNC_TYPE_1)(void);
typedef double (*FUNC_TYPE_2)(void);
typedef FUNC_TYPE_1 (*FUNC_TYPE_3)(FUNC_TYPE_2);
それよりも:
typedef int (*(*FUNC_TYPE_3)(double (*)(void)))(void);
cdecl
このようなことであなたを助けることができます:
cdecl> explain int (*FUNC_TYPE_1)(void)
declare FUNC_TYPE_1 as pointer to function (void) returning int
cdecl> explain double (*FUNC_TYPE_2)(void)
declare FUNC_TYPE_2 as pointer to function (void) returning double
cdecl> declare FUNC_TYPE_3 as pointer to function (pointer to function (void) returning double) returning pointer to function (void) returning int
int (*(*FUNC_TYPE_3)(double (*)(void )))(void )
そして、(実際には)まさに私が上記のクレイジーな混乱を生み出した方法です。
関数ポインタの typedef を理解するための非常に簡単な方法:
int add(int a, int b)
{
return (a+b);
}
typedef int (*add_integer)(int, int); //declaration of function pointer
int main()
{
add_integer addition = add; //typedef assigns a new variable i.e. "addition" to original function "add"
int c = addition(11, 11); //calling function via new variable
printf("%d",c);
return 0;
}
int add(int a, int b)
{
return (a+b);
}
int minus(int a, int b)
{
return (a-b);
}
typedef int (*math_func)(int, int); //declaration of function pointer
int main()
{
math_func addition = add; //typedef assigns a new variable i.e. "addition" to original function "add"
math_func substract = minus; //typedef assigns a new variable i.e. "substract" to original function "minus"
int c = addition(11, 11); //calling function via new variable
printf("%d\n",c);
c = substract(11, 5); //calling function via new variable
printf("%d",c);
return 0;
}
これの出力は次のとおりです。
22
6
両方の関数の宣言に同じ math_func 定義者が使用されていることに注意してください。
extern struct にも同じ typedef のアプローチを使用できます (他のファイルの sturuct を使用)。