2017-06-12 9 views
0

ちょっと私のCプログラムでコールバックを実装しました。Cコールバック - オプションの引数?

typedef void (*server_end_callback_t)(void *callbackArg); 

その後、私は私がvoid *callbackArgをスキップして、コードが正しく動作on_server_endコールバック関数の実装(エラーなし)に渡すことができるということに気づいた何

server->server_end_callback = on_sever_end; 

このコールバックを格納する構造体の内部変数を持っています。

void *のようないくつかの引数をスキップして、プロトタイプがそのような引数をとるコールバック関数を実装するのは正しいですか?

+1

私はあなたのコンパイラが文句を言っていないことに驚いています。いいえ、正しいものではありません。また、呼び出し先がスタックをクリーンアップするアーキテクチャによっては、間違いなくクラッシュします。 – spectras

+0

あなたのコンパイラは、次のようなものを与えるべきです:_warning:互換性のないポインタからの初期化type_エラーは... – LPs

+0

Cで「オプション」引数を安全かつ移植可能に指定する唯一の方法は省略記号 '...'を使用することです。 –

答えて

0

コードは引数を渡す規約のため正しく動作します。 Callerは、呼び出し先がいくつかの引数を要求していることを知っています。したがって、引数(レジスタまたはスタックのいずれか)をプラットフォーム上のABIに応じて準備します。次に、calleeはこれらのパラメータを使用するかどうかを指定します。呼び出し先から返された後、呼び出し側は必要に応じてスタックをクリーンアップします。それはミステリーです。

ただし、互換性のない関数を渡してこの特定の動作を悪用してはいけません。 -W -Wall -Werror(clang/gccと互換性があります)のオプションを使用してコードをコンパイルすることをお勧めします。このようなオプションを有効にすると、コンパイルエラーが発生します。

2

私はそれがCの観点からは未定義の動作だと考えていますが、使用している呼び出し規約のために機能します。

たとえば、AMD64 ABIによれば、最初の6つの引数はスタックではなくCPUレジスタを使用して呼び出し関数に渡されます。だから、呼び出し元も呼び出し先も最初の6つの引数についてはクリーンアップを必要とせず、正常に動作します。詳細情報については

the Wikipedia.

+0

注目:マイクロコントローラ用のさまざまなABIは、同様のレジスタ呼び出し規約を使用しますが、レジスタの量は関数形式によって異なります。たとえば、関数が 'void f(int x、int y)'であれば、正確に2つのレジスタにxとyを渡し、関数定義側では2つの期待されるレジスタだけを読み込みます。スタックが使用されていないにもかかわらず、関数形式から外れると、誤って生成されたコードの形で深刻なバグが発生します。 – Lundin

0

Cを参照してくださいには、関数の引数で、高速かつ緩い遊ぶの一定量を可能にします。従って

void (*fptr)(); 

は、「0個以上の引数をとる関数へのポインタ」を意味します。これは下位互換性のためですが、新しいCコードで使用することは賢明ではありません。

void (*fptr)(void *ptr) 
{ 
    /* don't use the void */ 
} 

/* in another scope */ 
(*fptr)(); /* call with no arguments */ 

他の方法でラウンドも、限り、あなたがvoid *を使用していないとして、働く、と私はそのことについて完全にはよく分からないのに、私は動作することが保証されると考えている(現代のマシン上の呼び出し規約ではレジスタの最初の引数を渡すので、ガーベジ・レジスタを取得するだけで済みます)。再び、それに頼ることは非常に悪い考えです。

void *を渡すことができます。このvoid *は、必要な数の引数を含む適切な型の構造体にキャストできます。これは良い考えであり、Cの柔軟性を賢明に利用しています。

0

void *のようないくつかの引数をスキップして、プロトタイプがそのような引数を取るコールバック関数を実装するのは正しいですか?

いいえ、それはありません。与えられた関数宣言を持つ関数は、異なる関数宣言の関数とは互換性がありません。このルールは、関数へのポインタにも適用されます。

pthread_create(..., my_callback, ...);などの関数があり、void* (*) (void*)タイプの関数ポインタを渡すと予想される場合は、別の形式の関数ポインタを渡すことはできません。これにより未定義の動作が呼び出され、コンパイラで不正なコードが生成される可能性があります。

つまり、関数ポインタの互換性は、多くのシステムで一般的で非標準的な拡張です。関数の形式が重要でなく、特定のコンパイラポートがそれをサポートするようにシステムの呼び出し規約が指定されていれば、そのようなコードはうまく動作するかもしれません。

しかし、このようなコードは移植性がなく、標準ではありません。できるだけ避けることが最善です。