Mengapa definisi fungsi pointer berfungsi dengan sejumlah ampersand '&' atau tanda bintang '*'?

216

Mengapa cara berikut ini berhasil?

void foo() {
    cout << "Foo to you too!\n";
};

int main() {
    void (*p1_foo)() = foo;
    void (*p2_foo)() = *foo;
    void (*p3_foo)() = &foo;
    void (*p4_foo)() = *&foo;
    void (*p5_foo)() = &*foo;
    void (*p6_foo)() = **foo;
    void (*p7_foo)() = **********************foo;

    (*p1_foo)();
    (*p2_foo)();
    (*p3_foo)();
    (*p4_foo)();
    (*p5_foo)();
    (*p6_foo)();
    (*p7_foo)();
}
Jimmy
sumber

Jawaban:

224

Ada beberapa bagian yang memungkinkan semua kombinasi operator ini bekerja dengan cara yang sama.

Alasan mendasar mengapa semua pekerjaan ini adalah bahwa suatu fungsi (seperti foo) secara implisit dapat dikonversi menjadi pointer ke fungsi tersebut. Inilah sebabnya mengapa void (*p1_foo)() = foo;berfungsi: foosecara implisit dikonversi menjadi pointer ke dirinya sendiri dan pointer yang ditugaskan p1_foo.

Unary &, ketika diterapkan pada suatu fungsi, menghasilkan pointer ke fungsi, sama seperti itu menghasilkan alamat suatu objek ketika diterapkan pada suatu objek. Untuk pointer ke fungsi biasa, selalu redundan karena konversi fungsi-ke-fungsi-pointer implisit. Bagaimanapun, ini sebabnya void (*p3_foo)() = &foo;bekerja.

Unary *, ketika diterapkan pada pointer fungsi, menghasilkan fungsi menunjuk-ke, sama seperti itu menghasilkan objek menunjuk-ke ​​ketika itu diterapkan pada pointer biasa ke objek.

Aturan-aturan ini bisa digabungkan. Pertimbangkan contoh kedua hingga terakhir Anda **foo:

  • Pertama, foosecara implisit dikonversi menjadi pointer ke dirinya sendiri dan yang pertama *diterapkan ke pointer fungsi itu, menghasilkan fungsi foolagi.
  • Kemudian, hasilnya secara implisit dikonversi menjadi pointer ke dirinya sendiri dan yang kedua *diterapkan, lagi-lagi menghasilkan fungsi foo.
  • Itu kemudian secara implisit dikonversi ke fungsi pointer lagi dan ditugaskan ke variabel.

Anda dapat menambahkan sebanyak yang *Anda suka, hasilnya selalu sama. Semakin banyak *, semakin meriah.

Kami juga dapat mempertimbangkan contoh kelima Anda &*foo:

  • Pertama, foosecara implisit dikonversi menjadi pointer ke dirinya sendiri; unary *diterapkan, menghasilkan foolagi.
  • Kemudian, &diterapkan ke foo, menghasilkan pointer ke foo, yang ditugaskan ke variabel.

Namun, &hanya dapat diterapkan ke fungsi, bukan ke fungsi yang telah dikonversi ke pointer fungsi (kecuali, tentu saja, pointer fungsi adalah variabel, dalam hal ini hasilnya adalah pointer-to-a-pointer- to-a-function; misalnya, Anda dapat menambahkan ke daftar Anda void (**pp_foo)() = &p7_foo;).

Inilah sebabnya mengapa &&footidak berfungsi: &foobukan fungsi; itu adalah pointer fungsi yang merupakan nilai. Namun, &*&*&*&*&*&*fooakan berfungsi, seperti yang akan terjadi &******&foo, karena dalam kedua ekspresi &itu selalu diterapkan pada fungsi dan bukan ke pointer fungsi nilai.

Perhatikan juga bahwa Anda tidak perlu menggunakan unary *untuk melakukan panggilan melalui penunjuk fungsi; keduanya (*p1_foo)();dan (p1_foo)();memiliki hasil yang sama, sekali lagi karena konversi fungsi-ke-fungsi-pointer.

James McNellis
sumber
2
@ Jimmy: Itu bukan referensi ke pointer fungsi, mereka hanya pointer fungsi. &foomengambil alamat foo, yang menghasilkan pointer fungsi menunjuk foo, seperti yang diharapkan.
Dennis Zickefoose
2
Anda juga tidak dapat mengaitkan &operator untuk objek: diberikan int p;, &pmenghasilkan pointer ke pdan merupakan ekspresi nilai; yang &Operator membutuhkan ekspresi lvalue.
James McNellis
12
Saya tidak setuju. Semakin banyak *, semakin kurang riang .
Seth Carnegie
28
Tolong jangan edit sintaksis contoh saya. Saya telah mengambil contoh-contoh yang sangat khusus untuk menunjukkan fitur-fitur bahasa.
James McNellis
7
Sebagai catatan tambahan, standar C secara eksplisit menyatakan bahwa kombinasi saling &*membatalkan (6.5.3.2): "The unary & operator yields the address of its operand."/ - / "If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted, except that the constraints on the operators still apply and the result is not an lvalue.".
Lundin
9

Saya pikir itu juga membantu untuk mengingat bahwa C hanyalah sebuah abstraksi untuk mesin yang mendasarinya dan ini adalah salah satu tempat di mana abstraksi itu bocor.

Dari perspektif komputer, fungsi hanyalah alamat memori yang, jika dijalankan, melakukan instruksi lainnya. Jadi fungsi dalam C itu sendiri dimodelkan sebagai alamat, yang mungkin mengarah ke desain yang fungsinya "sama" dengan alamat yang ditunjuknya.

madumlao
sumber
0

&dan *operasi idempoten pada simbol dinyatakan sebagai fungsi dalam C yang berarti func == *func == &func == *&funcdan karenanya*func == **func

Ini berarti bahwa jenisnya int ()sama dengan int (*)()sebagai parameter fungsi dan fungsi yang didefinisikan dapat diteruskan sebagai *func, funcatau &func. (&func)()sama dengan func(). Tautan Godbolt.

Suatu fungsi benar-benar sebuah alamat, oleh karena itu *dan &tidak memiliki makna, dan alih-alih menghasilkan kesalahan, kompiler memilih untuk menafsirkannya sebagai alamat func.

&pada simbol yang dideklarasikan sebagai fungsi pointer namun akan mendapatkan alamat dari pointer (karena sekarang memiliki tujuan terpisah), sedangkan funcpdan *funcpakan identik

Lewis Kelsey
sumber