web-dev-qa-db-ja.com

関数ポインターの逆参照はどのように発生しますか?

関数ポインターの逆参照がなぜ「何もしない」のはなぜですか?

これは私が話していることです:

#include<stdio.h>

void hello() { printf("hello"); }

int main(void) { 
    (*****hello)(); 
}

here に対するコメントから:

関数ポインタはうまく逆参照しますが、結果の関数指定子はすぐに関数ポインタに変換されます


そして答えから ここ

関数のポインターを(あなたの考えているように)参照解除するとは、データメモリと同じようにCODEメモリにアクセスすることを意味します。

関数ポインターは、そのように逆参照されることを想定していません。代わりに、呼び出されます。

「呼び出し」と並んで「参照解除」という名前を使用します。大丈夫です。

とにかく:Cは、関数名識別子と変数保持関数のポインターの両方が同じことを意味するように設計されています:CODEメモリへのアドレス。また、識別子または変数のcall()構文を使用して、そのメモリにジャンプできます。


どのようにexactly関数ポインタの逆参照が機能しますか?

65
Lazer

それはまったく正しい質問ではありません。 Cの場合、少なくとも正しい質問は

右辺値コンテキストの関数値はどうなりますか?

(右辺値コンテキストは、場所ではなく、値として使用する必要がある場所に名前または他の参照が表示される場所です。基本的には、割り当ての左側を除くすべての場所です。名前自体は-割り当ての手側。)

それでは、右辺値コンテキストの関数値はどうなりますか?元の関数値へのポインタに即座に暗黙的に変換されます。 *でそのポインターを逆参照すると、同じ関数値が再び返されます。これは、即座に暗黙的にポインターに変換されます。そして、これを何回でも好きなだけ行うことができます。

あなたが試すことができる2つの同様の実験:

  • lvalueコンテキスト(割り当ての左側)で関数ポインターを逆参照するとどうなりますか。 (答えは、関数が不変であることを念頭に置いておくと、期待するものに関するものになります。)

  • 配列値も左辺値コンテキストのポインターに変換されますが、配列へのポインターではなく、element型へのポインターに変換されます。したがって、それを逆参照すると、配列ではなく要素が得られ、表示される狂気は発生しません。

お役に立てれば。

追伸why関数値は暗黙的にポインターに変換されますが、その答えは、関数ポインターを使用するユーザーにとっては、&をどこでも使用しなくてもよいことです。二重の利便性もあります。呼び出し位置の関数ポインターは自動的に関数値に変換されるため、関数ポインターを介して呼び出すために*を記述する必要はありません。

P.P.S. C関数とは異なり、C++関数はオーバーロードされる可能性があり、セマンティクスがC++でどのように機能するかについてコメントする資格はありません。

50
Norman Ramsey

C++ 03§4.3/ 1:

関数型Tの左辺値は、型「Tへのポインター」の右辺値に変換できます。結果は、関数へのポインターです。

単項*演算子などの関数参照で無効な操作を試みる場合、言語が最初に試みるのは標準変換です。 intに追加するときにfloatを変換するようなものです。関数参照で*を使用すると、代わりに言語がポインターを取得します。このポインターは、例では正方形1です。

これが当てはまる別のケースは、関数ポインターを割り当てる場合です。

void f() {
    void (*recurse)() = f; // "f" is a reference; implicitly convert to ptr.
    recurse(); // call operator is defined for pointers
}

これはdoes n'tが他の方法で動作することに注意してください。

void f() {
    void (&recurse)() = &f; // "&f" is a pointer; ERROR can't convert to ref.
    recurse(); // OK - call operator is *separately* defined for references
}

関数参照変数は、それらが(理論的にはテストしたことはありませんが)囲みスコープで初期化された場合、間接分岐が不要である可能性があることをコンパイラーに示唆するため、素晴らしいです。

C99では、関数ポインターを逆参照すると、関数指定子が生成されます。 §6.3.2.1/ 4:

関数指定子は、関数タイプを持つ式です。 sizeof演算子または単項&演算子のオペランドである場合を除き、タイプが「関数を返すタイプ」の関数指定子は、タイプが「関数を返すタイプへのポインター」を持つ式に変換されます。

これはノーマンの答えに似ていますが、特にC99には右辺値の概念がありません。

7
Potatoswatter

コンパイラライターの立場になってください。関数ポインターには明確に定義された意味があり、マシンコードを表すバイトのblobへのポインターです。

プログラマーが関数ポインターを逆参照するときはどうしますか?マシンコードの最初の(または8)バイトを取得し、ポインターとして再解釈しますか?オッズは約20億対1で、これは機能しません。 UBを宣言しますか?その多くはすでに回っています。それとも、その試みを無視しますか?あなたは答えを知っています。

2
Hans Passant

関数ポインターの逆参照はどのように機能しますか?

2つのステップ。最初のステップはコンパイル時で、2番目のステップは実行時です。

ステップ1で、コンパイラーは、ポインターと、そのポインターが逆参照されるコンテキスト((*pFoo)()など)を持っているので、その状況のコード、ステップ2で使用されるコードを生成します。

ステップ2では、実行時にコードが実行されます。ポインターには、次に実行する機能を示すいくつかのバイトが含まれています。これらのバイトは何らかの形でCPUにロードされます。一般的なケースは、明示的なCALL [register]命令を持つCPUです。このようなシステムでは、関数ポインターは単にメモリ内の関数のアドレスであり、逆参照コードはレジスタにそのアドレスをロードし、その後にCALL [register]命令が続くだけです。

1
MSalters