質問
ということかしている関数を受け入れ void (*)(void*)
関数ポインタを使用してコールバック:
void do_stuff(void (*callback_fp)(void*), void* callback_arg);
現在の場機能のようになります:
void my_callback_function(struct my_struct* arg);
でいます。
do_stuff((void (*)(void*)) &my_callback_function, NULL);
私た この質問 また一部のCの基準を言うまでにキャスト"対応機能のポインタが見当たりませんの定義をどのような"対応機能のポインタ。
解決
どのC標準に関れば、キャスト関数ポインタをポインタ機能の異なるタイプを呼び出してい 未定義の動作.添付書類aを参照J.2(インフォメーション):
の動作は未定義は以下のいずれかの場合に限:
- ポインタを使用関数を呼び出しますタイプをクライアントに対応していないとの指摘を タイプ(6.3.2.3).
部6.3.2.3,8項を読み込み:
ポインタ機能の一つの類型に変換される可能性があり、ポインタ機能の他 タイプしてもらうことを目的として;の結果は、比較と同等のポインタです。場合に変換 ポインタの呼び出しに使用する機能タイプには対応しておりませんが-タイプ の挙動については、定義されていません。
つきキャスト関数ポインタを別の関数ポインタ型にキャストでも、電話でもします。
のの定義 対応 はやや複雑です。できるいくつかの6.7.5.3条第15:
二種機能の対応により、指定した対応を返しの種類127.
また、パラメータ型のリストである場合は、条数の パラメータと使用の省略記号はターミネーター;対応するパラメータは、 対応種類です。合型のパラメータ型のリストおよびその他のタイプには指定された 機能declaratorていない一部機能の定義および格納する空 識別子のリストパラメータのリストは持っていない省略記号はターミネーターの各 パラメータには対応し、その結果からの適用 デフォルトの引数セールスプロモーション合型のパラメータ型のリストおよびその他のタイプ 指定された機能定義される空の)識別名リストの双方は、 に同意のパラメータの型の型パラメータは、 に対応し、その結果から、デフォルトの引数 プロモーションのタイプに対応する識別子です。の決定タイプ 互換性および複合型、各パラメータ宣言された機能または配列 タイプとしての調整型および各パラメータ宣言された有資格の種類 として取り込まれ、絶版その宣言されています。
127)その両方の機能タイプ"古いスタイル"とは、メソッドのパラメータはない。
のルールを決定するかどうかについての対応に記述されて6.2.7、こんな見積もりをこちらかがわりとうるさいですけれども、長いものだとして 草案は、C99の基準(PDF).
当該規則は第6.7.5.1条第2項:
のための二つのポインタ型に対応、双方は同じ資格とは、ポインタに対応します。
そのため、 void*
互換性がありません と struct my_struct*
, は、関数ポインタ型 void (*)(void*)
に対応していない関数ポインタ型 void (*)(struct my_struct*)
, ので、この鋳造の関数ポインタは技術的に定義されます。
実際にはものまで、安心してぐら鋳物機能のポインタの場合があります。のx86呼び出し条約の引数に押され、スタックは、すべてのポインタと同じサイズ(4バイトx86または8バイトをx86_64).呼び出し関数ポインタを突き詰めれば押して、引数をスタックなどを間接的にジャンプ機能のポインタを対象とが明らかにせんという概念種類のマシンコードです。
んでいることも できない :
- キャスト間の関数ポインタの異なる呼び出しコンベンションに出かけていく。すべての空港を表示一部の空港を表をスタックで、クラッシュ、最悪の成功できる、黙々と巨大なぽっかりセキュリティホール.Windowsプログラミングよくパス機能のポインタです。Win32ーパーコールバック機能の利用
stdcall
呼び出し規約(マクロCALLBACK
,PASCAL
, は、WINAPI
すべての拡大しまいます。合格関数ポインタを使用する標準C呼び出し規約(cdecl
),悪いきます。 - C++では、キャストとクラスのメンバー関数ポインタと通常の関数ポインタ.この多ドまたはC++の開発には限.クラスメンバー機能の隠し
this
パラメータがキャストの会員機能を正機能ありませんthis
オブジェクトを使用、再度、悪いきます。
も悪いという考えが止まることもあり仕事にも定義されていない動作:
- 鋳造と機能のポインタと通常のポインタ(例えば鋳造
void (*)(void)
へvoid*
).関数ポインタのない限り、同じサイズとして通常ポイント以上のアーキテクチャもあれを含む追加の背景状況情報です。このう作りok x86が覚えていることで定義されます。
他のヒント
聞いてこの正確な同じ問題に関する一部のコードにファイルです。(GLibはコアライブラリはGNOMEプロジェクトで記述C.)があったと言われ、全体のスロットアンド'signalsの枠組みにより異なります。
全体のコードでは、数多くのインスタンスの鋳物から式(1)(2):
typedef int (*CompareFunc) (const void *a, const void *b)
typedef int (*CompareDataFunc) (const void *b, const void *b, void *user_data)
通常はチェーン-スルーの呼び出しのようになります:
int stuff_equal (GStuff *a,
GStuff *b,
CompareFunc compare_func)
{
return stuff_equal_with_data(a, b, (CompareDataFunc) compare_func, NULL);
}
int stuff_equal_with_data (GStuff *a,
GStuff *b,
CompareDataFunc compare_func,
void *user_data)
{
int result;
/* do some work here */
result = compare_func (data1, data2, user_data);
return result;
}
みち g_array_sort()
: http://git.gnome.org/browse/glib/tree/glib/garray.c
記述は、詳細が正しい-- の場合 い灯りに照らされ、昼間とは違っ基準委員会アダムとヨハンネス-賞与信のためのカンボジアのクメール-ルージュが研究に対する反応。しかし、の、このコード作品だけます。醸?そうです。このことを考え:ファイルを編集/作所/試験の膨大な数のプラットフォーム(Linux/Unix/Windows/mac OS X)の幅広いコンパイラ/志/kernelローダー(GCC/CLang/MSVC).基準買によるものだと考えられます。
い時間を考えるこれらの応答となります。ここでは私の結論
- を書いていてコールバックを図書館が、これはOK。とを条件とemptor--利用お客様の自己責任となります。
- なんです。
思考をより深く書いた後にこの対応をしたとしても、驚くことはないであれば、コードのためのCコンパイラでコンパイルを用いてこの同じリックを達成しています。そして(どれくらい?) 現代のCコンパイラはbootstrappedこのようなフレキシビリティが安心です。
より重要な問題の研究:できる人を探プラットフォーム-コンパイラ/リンカー/ローダがこのトリックな ない す。主要なブラウニポイントです。私はベットが組込みプロセッサシステムのないようにします。しかし、デスクトップ計算の(おそらくモバイル/タブレットは、このトリックがまだまだ多いと思われる。
ポイントは本当にあなたができるかどうかではありません。自明な解がある。
void my_callback_function(struct my_struct* arg);
void my_callback_helper(void* pv)
{
my_callback_function((struct my_struct*)pv);
}
do_stuff(&my_callback_helper);
それが本当に必要なの場合は、良いコンパイラは、あなたがそれを喜んでいると思います。その場合には、my_callback_helper用のコードを生成します。
あなたは互換性のある関数の型を持っている - 基本的には(それが現実で、より複雑だ:))。互換性は、さまざまな種類がありますが、それでも「これらの型はほぼ同じです」と言ってのいくつかのフォームを持つことができるようにちょうどより緩い「同じタイプ」と同じです。 C89では、例えば、2つの構造体は、彼らが他の点では同じであったが、ちょうど彼らの名前が違っていた場合の互換性でした。 C99はそれを変更しているように見えます。 (ところで、強くお勧め読書!) C根拠の文書するからの引用:
二つの異なる翻訳単位で構造体、共用体、または列挙型の宣言は、正式に翻訳単位がばらばらに自分自身をしているため、これらの宣言のテキストは、同じから来るのファイルが含まれていても、同じ型を宣言しないでください。二つのそのような宣言は十分に類似している場合、それらは互換性があるように規格は、このように、このようなタイプの追加の互換性ルールを指定します。
とはいえ - ええ、厳密ごdo_stuff機能や他の誰かがパラメータとしてvoid*
を持つ関数ポインタを使って関数を呼び出しますので、これは、未定義の動作ですが、あなたの関数は互換性のないパラメータがあります。しかし、それにもかかわらず、私はすべてのコンパイラはうめき声なしでそれをコンパイルして実行することを期待します。しかし、あなたはただ、あなたの実際の関数を呼び出す別の関数void*
を取って(とコールバック関数としてその登録)を有することにより、クリーナーを行うことができます。
、それはあなたが言及したコードを使用することは非常に細かいです。あなたが何か他のものにあなたのコールバック関数ポインタでdo_stuff実行したいときには、引数として、その後my_struct構造問題に実行思います。
私は私が仕事ではないだろうか示すことによって、それをより明確にすることができます願っています:
int my_number = 14;
do_stuff((void (*)(void*)) &my_callback_function, &my_number);
// my_callback_function will try to access int as struct my_struct
// and go nuts
や...
void another_callback_function(struct my_struct* arg, int arg2) { something }
do_stuff((void (*)(void*)) &another_callback_function, NULL);
// another_callback_function will look for non-existing second argument
// on the stack and go nuts
基本的に、あなたがいる限り、データは実行時に意味を作り続けて、何が好きへのポインタをキャストすることができます。
、彼らはその後、リターンにスタックをポップ、実行し、新しいコードの場所にジャンプし、スタック上の特定の項目を押してください。あなたの関数ポインタが同じ戻り値の型と引数の同じ数/サイズを持つ関数を記述した場合、あなたは大丈夫でなければなりません。
このように、私はあなたがそう安全に行うことができるはずだと思います。
ボイドポインタはポインタの他のタイプと互換性があります。それはどのようにmallocとMEM機能(memcpy
、memcmp
)仕事のバックボーンです。典型的には、(むしろC ++より)CにNULL
は((void *)0)
のように定義されたマクロである。
C99で(項目1)6.3.2.3を見てください:
voidへのポインタは、任意の不完全またはオブジェクトタイプに、またはポインタから変換することができる