11 Pertanyaan: Bagaimana fungsi pointer dalam C berfungsi?

pertanyaan dibuat di Fri, May 8, 2009 12:00 AM

Saya punya pengalaman akhir-akhir ini dengan pointer fungsi di C.

Jadi, melanjutkan tradisi menjawab pertanyaan Anda sendiri, saya memutuskan untuk membuat ringkasan kecil dari hal-hal mendasar, bagi mereka yang membutuhkan penyelaman cepat ke subjek.

    
1130
  1. Juga: Untuk sedikit analisis mendalam tentang pointer C, lihat blogs.oracle.com/ksplice/entry/the_ksplice_pointer_challenge . Juga, Pemrograman dari Bawah ke Atas menunjukkan cara kerjanya pada level mesin. Memahami "model memori" C sangat berguna untuk memahami cara kerja pointer C.
    2013-05-22 11: 17: 14Z
  2. Info hebat. Dengan judulnya, saya akan berharap untuk benar-benar melihat penjelasan tentang bagaimana "fungsi pointer berfungsi", bukan bagaimana mereka dikodekan :)
    2014-08-28 12: 39: 46Z
  3. "Bagaimana fungsi pointer dalam C bekerja?" Baiklah, terima kasih.
    2014-11-17 19: 07: 58Z
11 Jawaban                              11                         

Function pointer di C

Mari kita mulai dengan fungsi dasar yang akan kita arahkan ke :

 
int addInt(int n, int m) {
    return n+m;
}

Hal pertama, mari kita tentukan pointer ke fungsi yang menerima 2 int s dan mengembalikan int:

 
int (*functionPtr)(int,int);

Sekarang kita dapat dengan aman menunjuk ke fungsi kita:

 
functionPtr = &addInt;

Sekarang kita memiliki sebuah pointer ke fungsi, mari kita gunakan:

 
int sum = (*functionPtr)(2, 3); // sum == 5

Melewati pointer ke fungsi lain pada dasarnya sama:

 
int add2to3(int (*functionPtr)(int, int)) {
    return (*functionPtr)(2, 3);
}

Kita juga bisa menggunakan fungsi pointer dalam mengembalikan nilai (cobalah untuk menjaga, itu menjadi berantakan):

 
// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
    printf("Got parameter %d", n);
    int (*functionPtr)(int,int) = &addInt;
    return functionPtr;
}

Tapi jauh lebih baik menggunakan typedef:

 
typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef

myFuncDef functionFactory(int n) {
    printf("Got parameter %d", n);
    myFuncDef functionPtr = &addInt;
    return functionPtr;
}
    
1370
2016-12-29 22: 23: 52Z
  1. Terima kasih atas informasinya. Bisakah Anda menambahkan beberapa wawasan tentang di mana pointer fungsi digunakan atau secara khusus bermanfaat?
    2009-05-08 15: 55: 02Z
  2. "functionPtr = & addInt;" dapat juga ditulis (dan seringkali) sebagai "functionPtr = addInt;" yang juga berlaku karena standar mengatakan bahwa nama fungsi dalam konteks ini dikonversi ke alamat fungsi.
    2009-05-09 14: 39: 44Z
  3. hlovdal, dalam konteks ini menarik untuk menjelaskan bahwa inilah yang memungkinkan seseorang untuk menulis functionPtr = **************** ** tambahkanInt;
    2009-05-10 17: 54: 30Z
  4. @ Rich.Carpenter Saya tahu ini terlambat 4 tahun, tetapi saya pikir orang lain akan mendapat manfaat dari ini: Pointer fungsi berguna untuk melewatkan fungsi sebagai parameter ke fungsi lain . Butuh banyak pencarian untuk menemukan jawaban itu untuk beberapa alasan aneh. Jadi pada dasarnya, ini memberikan fungsionalitas kelas semu C.
    2013-10-13 02: 28: 01Z
  5. @ Rich.Carpenter: pointer fungsi bagus untuk deteksi CPU runtime. Miliki banyak versi dari beberapa fungsi untuk memanfaatkan SSE, popcnt, AVX, dll. Saat memulai, setel pointer fungsi Anda ke versi terbaik dari setiap fungsi untuk CPU saat ini. Dalam kode Anda yang lain, panggil saja melalui pointer fungsi alih-alih memiliki cabang bersyarat pada fitur CPU di mana-mana. Maka Anda bisa melakukan comenerapkan logika tentang memutuskan itu dengan baik, meskipun CPU ini mendukung pshufb, itu lambat, jadi implementasi sebelumnya masih lebih cepat. x264 /x265 menggunakan ini secara luas, dan bersifat open source.
    2015-08-30 02: 22: 31Z

Function pointer di C dapat digunakan untuk melakukan pemrograman berorientasi objek dalam C.

Misalnya, baris berikut ditulis dalam C:

 
String s1 = newString();
s1->set(s1, "hello");

Ya, -> dan tidak adanya operator new adalah hadiah mati, tapi sepertinya menyiratkan bahwa kami mengatur teks dari kelas String menjadi "hello".

Dengan menggunakan pointer fungsi, dimungkinkan untuk meniru metode dalam C .

Bagaimana ini dicapai?

Kelas String sebenarnya adalah struct dengan banyak fungsi pointer yang bertindak sebagai cara untuk mensimulasikan metode. Berikut ini adalah deklarasi sebagian dari kelas String:

 
typedef struct String_Struct* String;

struct String_Struct
{
    char* (*get)(const void* self);
    void (*set)(const void* self, char* value);
    int (*length)(const void* self);
};

char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);

String newString();

Seperti yang bisa dilihat, metode pada kelas String sebenarnya adalah fungsi pointer ke fungsi yang dideklarasikan. Dalam menyiapkan instance String, fungsi newString dipanggil untuk mengatur pointer fungsi ke fungsi masing-masing:

 
String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

Misalnya, fungsi getString yang dipanggil dengan memanggil metode get didefinisikan sebagai berikut:

 
char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

Satu hal yang dapat diperhatikan adalah bahwa tidak ada konsep turunan objek dan memiliki metode yang sebenarnya merupakan bagian dari objek, jadi "objek mandiri" harus diteruskan pada setiap doa. (Dan internal hanyalah struct tersembunyi yang dihilangkan dari daftar kode sebelumnya - ini adalah cara melakukan penyembunyian informasi, tetapi itu tidak relevan dengan pointer fungsi.)

Jadi, daripada bisa melakukan s1->set("hello");, seseorang harus meneruskan objek untuk melakukan aksi pada s1->set(s1, "hello").

Dengan penjelasan minor yang harus menyerahkan referensi kepada Anda sendiri, kami akan pindah ke bagian berikutnya, yaitu warisan di C .

Katakanlah kita ingin membuat subclass dari String, katakanlah ImmutableString. Agar string tidak dapat diubah, metode set tidak akan dapat diakses, sembari mempertahankan akses ke get dan length, dan memaksa "konstruktor" untuk menerima char*:

 
typedef struct ImmutableString_Struct* ImmutableString;

struct ImmutableString_Struct
{
    String base;

    char* (*get)(const void* self);
    int (*length)(const void* self);
};

ImmutableString newImmutableString(const char* value);

Pada dasarnya, untuk semua subclass, metode yang tersedia adalah pointer fungsi sekali lagi. Kali ini, deklarasi untuk metode set tidak ada, oleh karena itu, tidak dapat dipanggil di ImmutableString.

Sedangkan untuk implementasi ImmutableString, satu-satunya kode yang relevan adalah fungsi "konstruktor", newImmutableString:

 
ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = self->base->length;

    self->base->set(self->base, (char*)value);

    return self;
}

Dalam membuat instance ImmutableString, fungsi pointer ke metode get dan length sebenarnya merujuk pada metode String.get dan String.length, dengan melalui variabel base yang merupakan objek String yang disimpan secara internal.

Penggunaan pointer fungsi dapat mencapai pewarisan metode dari superclass.

Kami dapat melanjutkan ke polimorfisme dalam C .

Jika misalnya kita ingin mengubah perilaku metode length untuk mengembalikan 0 sepanjang waktu di kelas ImmutableString karena alasan tertentu, semua yang harus dilakukan adalah dengan:

  1. Tambahkan fungsi yang akan berfungsi sebagai metode utama length.
  2. Pergi ke "constructor" dan atur pointer fungsi ke metode length yang utama.

Menambahkan metode length utama di ImmutableString dapat dilakukan dengan menambahkan lengthOverrideMethod:

 
int lengthOverrideMethod(const void* self)
{
    return 0;
}

Kemudian, penunjuk fungsi untuk metode length dalam konstruktor dihubungkan ke lengthOverrideMethod:

 
ImmutableString newImmutableString(const char* value)
{
    ImmutableString self = (ImmutableString)malloc(sizeof(struct ImmutableString_Struct));

    self->base = newString();

    self->get = self->base->get;
    self->length = &lengthOverrideMethod;

    self->base->set(self->base, (char*)value);

    return self;
}

Sekarang, daripada memiliki perilaku yang identik untuk metode length di kelas ImmutableString sebagai kelas String, sekarang metode length akan merujuk pada perilaku yang didefinisikan dalam fungsi lengthOverrideMethod.

Saya harus menambahkan penafian bahwa saya masih belajar cara menulis dengan gaya pemrograman berorientasi objek dalam C, jadi mungkin ada poin yang tidak saya jelaskan dengan baik, atau mungkin hanya meleset dalam hal cara terbaik untuk mengimplementasikan OOP dalam C. Tetapi tujuan saya adalah mencoba mengilustrasikan salah satu dari banyak kegunaan pointer fungsi.

Untuk informasi lebih lanjut tentang cara melakukan pemrograman berorientasi objek dalam C, silakan lihat pertanyaan berikut:

285
2017-05-23 12:18:30Z
  1. Jawaban ini mengerikan! Tidak hanya itu menyiratkan bahwa OO entah bagaimana tergantung pada notasi titik, itu juga mendorong memasukkan sampah ke objek Anda!
    2012-09-16 14: 30: 47Z
  2. Ini adalah OO baik-baik saja, tetapi tidak berada di dekat C-style OO. Apa yang telah Anda implementasikan secara salah adalah OO berbasis prototipe gaya-Javascript. Untuk mendapatkan OO gaya C ++ /Pascal, Anda harus: 1. Memiliki const struct untuk tabel virtual setiap kelas dengan anggota virtual. 2. Memiliki pointer ke struct di objek polimorfik. 3. Panggil metode virtual melalui tabel virtual, dan semua metode lain secara langsung - biasanya dengan tetap menggunakan konvensi penamaan fungsi ClassName_methodName. Hanya dengan begitu Anda mendapatkan biaya runtime dan penyimpanan yang sama seperti yang Anda lakukan di C ++ dan Pascal.
    2013-03-18 21: 53: 15Z
  3. Bekerja OO dengan bahasa yang tidak dimaksudkan sebagai OO selalu merupakan ide yang buruk. Jika Anda ingin OO dan masih memiliki C hanya bekerja dengan C ++.
    2013-07-04 15 15: 21: 34Z
  4. @ rbaleksandar Katakan itu kepada pengembang kernel Linux. "selalu ide yang buruk" benar-benar pendapatmu, yang dengan tegas aku tidak setuju.
    2015-04-30 12: 31: 24Z
  5. Saya suka jawaban ini tetapi jangan menggunakan malloc
    2016-09-29 14: 41: 26Z

Panduan untuk dipecat: Cara menyalahgunakan pointer fungsi di GCC pada mesin x86 dengan mengkompilasi kode Anda dengan tangan:

String literal ini adalah byte dari kode mesin x86 32-bit. 0xC3 adalah instruksi x86 ret .

Anda biasanya tidak menulis ini dengan tangan, Anda akan menulis dalam bahasa assembly dan kemudian menggunakan assembler seperti nasm untuk merakitnya menjadi biner datar yang Anda hexdump menjadi string C literal.

  1. Mengembalikan nilai saat ini pada register EAX

     
    int eax = ((int(*)())("\xc3 <- This returns the value of the EAX register"))();
    
  2. Tulis fungsi swap

     
    int a = 10, b = 20;
    ((void(*)(int*,int*))"\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b")(&a,&b);
    
  3. Tulis penghitung for-loop ke 1000, panggil beberapa fungsi setiap kali

     
    ((int(*)())"\x66\x31\xc0\x8b\x5c\x24\x04\x66\x40\x50\xff\xd3\x58\x66\x3d\xe8\x03\x75\xf4\xc3")(&function); // calls function with 1->1000
    
  4. Anda bahkan dapat menulis fungsi rekursif yang diperhitungkan hingga 100

     
    const char* lol = "\x8b\x5c\x24\x4\x3d\xe8\x3\x0\x0\x7e\x2\x31\xc0\x83\xf8\x64\x7d\x6\x40\x53\xff\xd3\x5b\xc3\xc3 <- Recursively calls the function at address lol.";
    i = ((int(*)())(lol))(lol);
    

Perhatikan bahwa kompiler menempatkan string literal di bagian .rodata (atau .rdata di Windows), yang ditautkan sebagai bagian dari segmen teks (bersama dengan kode untuk fungsi).

Segmen teks memiliki izin Baca + Exec, jadi casting string literal ke pointer berfungsi berfungsi tanpa perlu mprotect() atau VirtualProtect() panggilan sistem seperti yang Anda perlukan untuk memori yang dialokasikan secara dinamis. (Atau gcc -z execstack menautkan program dengan tumpukan + segmen data + tumpukan dapat dieksekusi, sebagai peretasan cepat.)


Untuk membongkar ini, Anda dapat mengkompilasi ini untuk memberi label pada byte, dan menggunakan disassembler.

 
// at global scope
const char swap[] = "\x8b\x44\x24\x04\x8b\x5c\x24\x08\x8b\x00\x8b\x1b\x31\xc3\x31\xd8\x31\xc3\x8b\x4c\x24\x04\x89\x01\x8b\x4c\x24\x08\x89\x19\xc3 <- This swaps the values of a and b";

Mengkompilasi dengan gcc -c -m32 foo.c dan membongkar dengan objdump -D -rwC -Mintel, kita bisa mendapatkan perakitan, dan mengetahui bahwa kode ini melanggar ABI dengan mengalahkan EBX (register yang diawetkan dengan panggilan) dan umumnya tidak efisien.

 
00000000 <swap>:
   0:   8b 44 24 04             mov    eax,DWORD PTR [esp+0x4]   # load int *a arg from the stack
   4:   8b 5c 24 08             mov    ebx,DWORD PTR [esp+0x8]   # ebx = b
   8:   8b 00                   mov    eax,DWORD PTR [eax]       # dereference: eax = *a
   a:   8b 1b                   mov    ebx,DWORD PTR [ebx]
   c:   31 c3                   xor    ebx,eax                # pointless xor-swap
   e:   31 d8                   xor    eax,ebx                # instead of just storing with opposite registers
  10:   31 c3                   xor    ebx,eax
  12:   8b 4c 24 04             mov    ecx,DWORD PTR [esp+0x4]  # reload a from the stack
  16:   89 01                   mov    DWORD PTR [ecx],eax     # store to *a
  18:   8b 4c 24 08             mov    ecx,DWORD PTR [esp+0x8]
  1c:   89 19                   mov    DWORD PTR [ecx],ebx
  1e:   c3                      ret    

  not shown: the later bytes are ASCII text documentation
  they're not executed by the CPU because the ret instruction sends execution back to the caller

Kode mesin ini akan (mungkin) bekerja dalam kode 32-bit pada Windows, Linux, OS X, dan sebagainya: konvensi pemanggilan default pada semua OS yang lulus argumen pada stack alih-alih lebih efisien dalam register. Tetapi EBX terpelihara dengan panggilan dalam semua konvensi panggilan biasa, jadi menggunakannya sebagai register awal tanpa menyimpan /mengembalikannya dapat dengan mudah membuat pemanggil macet.

    
215
2018-10-09 13: 58: 10Z
  1. Catatan: ini tidak berfungsi jika Pencegahan Eksekusi Data diaktifkan (mis. pada Windows XP SP2 +), karena string C biasanya tidak ditandai sebagai executable.
    2013-02-12 05: 53: 45Z
  2. Hai Matt! Bergantung pada level optimisasi, GCC akan sering memasukkan konstanta string ke dalam segmen TEXT, jadi ini akan berfungsi bahkan pada versi windows yang lebih baruasalkan Anda tidak melarang jenis optimasi ini. (IIRC, versi MINGW pada saat posting saya lebih dari dua tahun yang lalu inline literal string pada tingkat optimasi default)
    2014-01-02 06: 20: 51Z
  3. dapatkah seseorang menjelaskan apa yang terjadi di sini? Apa literal string yang tampak aneh itu?
    2014-01-20 10: 17: 40Z
  4. @ ajay Sepertinya dia menulis nilai hexidecimal mentah (misalnya '\x00' sama dengan '/0', keduanya sama dengan 0) ke dalam sebuah string, lalu melemparkan string ke dalam pointer fungsi C, kemudian mengeksekusi pointer fungsi C karena dia adalah iblis.
    2014-02-21 21: 27: 59Z
  5. hai FUZxxl, saya pikir ini mungkin bervariasi berdasarkan pada kompiler dan versi sistem operasi. Kode di atas tampaknya berjalan dengan baik di codepad.org; codepad.org/FMSDQ3ME
    2014-03-13 00: 48: 21Z

Salah satu kegunaan favorit saya untuk pointer fungsi adalah iterator yang murah dan mudah -

 
#include <stdio.h>
#define MAX_COLORS  256

typedef struct {
    char* name;
    int red;
    int green;
    int blue;
} Color;

Color Colors[MAX_COLORS];


void eachColor (void (*fp)(Color *c)) {
    int i;
    for (i=0; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}

int main() {
    Colors[0].name="red";
    Colors[0].red=255;
    Colors[1].name="blue";
    Colors[1].blue=255;
    Colors[2].name="black";

    eachColor(printColor);
}
    
104
2012-07-24 16: 40: 57Z
  1. Anda juga harus meneruskan sebuah pointer ke data yang ditentukan pengguna jika Anda ingin mengekstrak output apa pun dari iterasi (pikirkan penutupan).
    2012-09-16 14: 32: 52Z
  2. Setuju. Semua iterator saya terlihat seperti ini: int (*cb)(void *arg, ...). Nilai pengembalian iterator juga membuat saya berhenti lebih awal (jika bukan nol).
    2015-04-30 12: 35: 25Z

Pointer fungsi menjadi mudah untuk dideklarasikan setelah Anda memiliki deklarator dasar:

  • id: ID: ID is a
  • Pointer: *D: D pointer ke
  • Fungsi: D(<parameters>): Fungsi D mengambil < parameter > yang kembali

Sementara D adalah deklarator lain yang dibuat menggunakan aturan yang sama. Pada akhirnya, di suatu tempat, diakhiri dengan ID (lihat di bawah untuk contoh), yang merupakan nama entitas yang dinyatakan. Mari kita coba membangun fungsi dengan mengambil pointer ke fungsi yang tidak mengambil apa pun dan mengembalikan int, dan mengembalikan sebuah pointer ke fungsi mengambil char dan mengembalikan int. Dengan tipe-def seperti ini

 
typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

Seperti yang Anda lihat, cukup mudah untuk membuatnya menggunakan typedefs. Tanpa typedef, tidak sulit dengan aturan deklarator di atas, diterapkan secara konsisten. Seperti yang Anda lihat, saya melewatkan bagian yang ditunjuk oleh pointer, dan fungsi yang dikembalikan. Itulah yang muncul di bagian paling kiri dari deklarasi, dan tidak menarik: itu ditambahkan pada akhirnya jika seseorang sudah membangun deklarator. Ayo lakukan itu. Membangunnya secara konsisten, pertama bertele-tele - menunjukkan struktur menggunakan [ dan ]:

 
function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

Seperti yang Anda lihat, orang dapat mendeskripsikan suatu tipe sepenuhnya dengan menambahkan deklarator satu demi satu. Konstruksi dapat dilakukan dengan dua cara. Salah satunya adalah bottom-up, dimulai dengan hal yang sangat benar (daun) dan bekerja hingga pengidentifikasi. Cara lainnya adalah top-down, mulai dari pengenal, bekerja hingga daun. Saya akan menunjukkan dua arah.

Bottom Up

Konstruksi dimulai dengan benda di kanan: Benda dikembalikan, yang merupakan fungsi pengambilan char. Untuk menjaga agar deklarator tetap berbeda, saya akan menomorinya:

 
D1(char);

Memasukkan parameter char secara langsung, karena sepele. Menambahkan pointer ke deklarator dengan mengganti D1 oleh *D2. Perhatikan bahwa kita harus membungkus tanda kurung sekitar *D2. Itu bisa diketahui dengan melihat prioritas *-operator dan operator fungsi-panggilan (). Tanpa tanda kurung kami, kompiler akan membacanya sebagai *(D2(char p)). Tapi itu tidak akan menjadi pengganti D1 oleh *D2 lagi, tentu saja. Tanda kurung selalu diizinkan di sekitar deklarators. Jadi, Anda tidak melakukan kesalahan jika Anda menambahkannya terlalu banyak, sebenarnya.

 
(*D2)(char);

Jenis pengembalian sudah selesai! Sekarang, mari kita ganti D2 dengan fungsi declarator fungsi mengambil <parameters> kembali , yang merupakan D3(<parameters>) yang kita sekarang.

 
(*D3(<parameters>))(char)

Perhatikan bahwa tidak ada tanda kurung yang diperlukan, karena kami ingin D3 menjadi deklarator fungsi dan bukan deklarator pointer kali ini. Hebat, satu-satunya yang tersisa adalah parameter untuk itu. Parameter dilakukan persis sama seperti yang kita lakukan pada tipe pengembalian, hanya dengan char diganti dengan void. Jadi saya akan menyalinnya:

 
(*D3(   (*ID1)(void)))(char)

Saya telah mengganti D2 dengan ID1, karena kami selesai dengan parameter itu (itu sudah menjadi pointer ke fungsi - tidak perlu deklarator lain). ID1 akan menjadi nama parameter. Sekarang, saya katakan di atas pada bagian akhir menambahkan tipe yang diubah oleh semua deklarator - yang muncul di bagian paling kiri dari setiap deklarasi. Untuk fungsi, itu menjadi tipe pengembalian. Untuk pointer yang menunjuk untuk mengetik dll ... Sangat menarik ketika menuliskan tipe, itu akan muncul dalam urutan yang berlawanan, di bagian paling kanan :) Pokoknya, menggantikannya menghasilkan deklarasi lengkap. Kedua kali int tentu saja.

 
int (*ID0(int (*ID1)(void)))(char)

Saya telah memanggil pengidentifikasi fungsi ID0 dalam contoh itu.

Top Down

Ini dimulai dari pengidentifikasi di paling kiri dalam deskripsi jenisnya, membungkus deklarator itu saat kami berjalan melewati kanan. Mulai dengan fungsi mengambil < parameter, > mengembalikan

 
ID0(<parameters>)

Hal berikutnya dalam deskripsi (setelah "kembali") adalah penunjuk ke . Mari menggabungkannya:

 
*ID0(<parameters>)

Kemudian hal berikutnya adalah fungsi mengambil < parameter, > mengembalikan . Parameternya adalah char sederhana, jadi kami langsung memasukkannya lagi, karena ini benar-benar sepele.

 
(*ID0(<parameters>))(char)

Perhatikan tanda kurung yang kami tambahkan, karena kami ingin lagi * mengikat terlebih dahulu, dan lalu (char). Kalau tidak, ia akan membaca fungsi mengambil < parameter, > mengembalikan fungsi ... . Noes, fungsi yang mengembalikan fungsi bahkan tidak diperbolehkan.

Sekarang kita hanya perlu meletakkan < parameter >. Saya akan menunjukkan versi singkat dari deriveration, karena saya pikir Anda sudah memiliki ide untuk melakukannya.

 
pointer to: *ID1
... function taking void returning: (*ID1)(void)

Masukkan int sebelum deklarator seperti yang kami lakukan dengan bottom-up, dan kami selesai

 
int (*ID0(int (*ID1)(void)))(char)

Yang menyenangkan

Apakah bottom-up atau top-down lebih baik? Saya sudah terbiasa dengan bottom-up, tetapi beberapa orang mungkin lebih nyaman dengan top-down. Ini masalah selera saya pikir. Secara kebetulan, jika Anda menerapkan semua operator dalam deklarasi itu, Anda akan mendapatkan int:

 
int v = (*ID0(some_function_pointer))(some_char);

Itu adalah properti bagus deklarasi di C: Deklarasi ini menegaskan bahwa jika operator tersebut digunakan dalam ekspresi menggunakan pengenal, maka ia menghasilkan tipe di sebelah kiri. Seperti itu untuk array juga.

Semoga Anda menyukai tutorial kecil ini! Sekarang kita dapat menautkan ini ketika orang bertanya-tanya tentang sintaksis deklarasi fungsi yang aneh. Saya mencoba untuk menempatkan internal C sesedikit mungkin. Jangan ragu untuk mengedit /memperbaiki hal-hal di dalamnya.

    
24
2009-05-09 13: 38: 17Z

Penggunaan lain yang bagus untuk fungsi pointer:
Beralih antar versi tanpa rasa sakit

Sangat mudah digunakan ketika Anda menginginkan fungsi yang berbeda di waktu yang berbeda, atau fase pengembangan yang berbeda. Misalnya, saya sedang mengembangkan aplikasi pada komputer host yang memiliki konsol, tetapi rilis akhir dari perangkat lunak akan diletakkan pada Avnet ZedBoard (yang memiliki port untuk tampilan dan konsol, tetapi tidak diperlukan /diinginkan untuk rilis final). Jadi selama pengembangan, saya akan menggunakan printf untuk melihat status dan pesan kesalahan, tetapi ketika saya selesai, saya tidak ingin apapun dicetak. Inilah yang telah saya lakukan:

version.h

 
// First, undefine all macros associated with version.h
#undef DEBUG_VERSION
#undef RELEASE_VERSION
#undef INVALID_VERSION


// Define which version we want to use
#define DEBUG_VERSION       // The current version
// #define RELEASE_VERSION  // To be uncommented when finished debugging

#ifndef __VERSION_H_      /* prevent circular inclusions */
    #define __VERSION_H_  /* by using protection macros */
    void board_init();
    void noprintf(const char *c, ...); // mimic the printf prototype
#endif

// Mimics the printf function prototype. This is what I'll actually 
// use to print stuff to the screen
void (* zprintf)(const char*, ...); 

// If debug version, use printf
#ifdef DEBUG_VERSION
    #include <stdio.h>
#endif

// If both debug and release version, error
#ifdef DEBUG_VERSION
#ifdef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

// If neither debug or release version, error
#ifndef DEBUG_VERSION
#ifndef RELEASE_VERSION
    #define INVALID_VERSION
#endif
#endif

#ifdef INVALID_VERSION
    // Won't allow compilation without a valid version define
    #error "Invalid version definition"
#endif

Di version.c saya akan mendefinisikan 2 prototipe fungsi yang ada di version.h

version.c

 
#include "version.h"

/*****************************************************************************/
/**
* @name board_init
*
* Sets up the application based on the version type defined in version.h.
* Includes allowing or prohibiting printing to STDOUT.
*
* MUST BE CALLED FIRST THING IN MAIN
*
* @return    None
*
*****************************************************************************/
void board_init()
{
    // Assign the print function to the correct function pointer
    #ifdef DEBUG_VERSION
        zprintf = &printf;
    #else
        // Defined below this function
        zprintf = &noprintf;
    #endif
}

/*****************************************************************************/
/**
* @name noprintf
*
* simply returns with no actions performed
*
* @return   None
*
*****************************************************************************/
void noprintf(const char* c, ...)
{
    return;
}

Perhatikan bagaimana pointer fungsi dibuat prototipe di version.h sebagai

void (* zprintf)(const char *, ...);

Ketika direferensikan dalam aplikasi, itu akan mulai mengeksekusi di mana pun itu menunjuk, yang belum didefinisikan.

Di version.c, perhatikan di fungsi board_init() di mana zprintf ditetapkan fungsi unik (yang tanda tangannya fungsinya cocok) tergantung pada versi yang ditentukan di version.h

zprintf = &printf; zprintf memanggil printf untuk keperluan debugging

atau

zprintf = &noprint; zprintf baru saja kembali dan tidak akan menjalankan kode yang tidak perlu

Menjalankan kode akan terlihat seperti ini:

mainProg.c

 
#include "version.h"
#include <stdlib.h>
int main()
{
    // Must run board_init(), which assigns the function
    // pointer to an actual function
    board_init();

    void *ptr = malloc(100); // Allocate 100 bytes of memory
    // malloc returns NULL if unable to allocate the memory.

    if (ptr == NULL)
    {
        zprintf("Unable to allocate memory\n");
        return 1;
    }

    // Other things to do...
    return 0;
}

Kode di atas akan menggunakan printf jika dalam mode debug, atau tidak melakukan apa pun jika dalam mode rilis. Ini jauh lebih mudah daripada melalui seluruh proyek dan mengomentari atau menghapus kode. Yang perlu saya lakukan adalah mengubah versi di version.h dan kode akan melakukan sisanya!

    
23
2013-06-11 15: 36: 22Z
  1. Anda akan kehilangan banyak waktu kinerja. Sebagai gantinya, Anda dapat menggunakan makro yang mengaktifkan dan menonaktifkan bagian kode berdasarkan Debug /Rilis.
    2018-05-11 13: 41: 45Z

Function pointer biasanya ditentukan oleh typedef, dan digunakan sebagai param & nilai pengembalian.

Di atas jawaban sudah banyak dijelaskan, saya hanya memberikan contoh lengkap:

 
#include <stdio.h>

#define NUM_A 1
#define NUM_B 2

// define a function pointer type
typedef int (*two_num_operation)(int, int);

// an actual standalone function
static int sum(int a, int b) {
    return a + b;
}

// use function pointer as param,
static int sum_via_pointer(int a, int b, two_num_operation funp) {
    return (*funp)(a, b);
}

// use function pointer as return value,
static two_num_operation get_sum_fun() {
    return &sum;
}

// test - use function pointer as variable,
void test_pointer_as_variable() {
    // create a pointer to function,
    two_num_operation sum_p = &sum;
    // call function via pointer
    printf("pointer as variable:\t %d + %d = %d\n", NUM_A, NUM_B, (*sum_p)(NUM_A, NUM_B));
}

// test - use function pointer as param,
void test_pointer_as_param() {
    printf("pointer as param:\t %d + %d = %d\n", NUM_A, NUM_B, sum_via_pointer(NUM_A, NUM_B, &sum));
}

// test - use function pointer as return value,
void test_pointer_as_return_value() {
    printf("pointer as return value:\t %d + %d = %d\n", NUM_A, NUM_B, (*get_sum_fun())(NUM_A, NUM_B));
}

int main() {
    test_pointer_as_variable();
    test_pointer_as_param();
    test_pointer_as_return_value();

    return 0;
}
    
14
2019-06-08 06: 56: 02Z

Salah satu kegunaan besar untuk pointer fungsi di C adalah untuk memanggil fungsi yang dipilih saat run-time. Misalnya, pustaka run-time C memiliki dua rutin, qsort dan bsearch , yang membawa penunjuk ke fungsi yang dipanggil untuk membandingkan dua item menjadi disortir; ini memungkinkan Anda untuk mengurutkan atau mencari, masing-masing, apa pun, berdasarkan kriteria apa pun yang ingin Anda gunakan.

Contoh yang sangat mendasar, jika ada satu fungsi bernama print(int x, int y) yang pada gilirannya mungkin perlu memanggil fungsi (add() atau sub(), yang memiliki tipe yang sama) maka apa yang akan kita lakukan, kita akan menambahkan satu argumen penunjuk fungsi ke fungsi print() seperti yang ditunjukkan di bawah ini:

 
#include <stdio.h>

int add()
{
   return (100+10);
}

int sub()
{
   return (100-10);
}

void print(int x, int y, int (*func)())
{
    printf("value is: %d\n", (x+y+(*func)()));
}

int main()
{
    int x=100, y=200;
    print(x,y,add);
    print(x,y,sub);

    return 0;
}

Outputnya adalah:

  Nilai

​​adalah: 410 Situs   nilainya: 390

    
13
2019-03-13 17: 09: 26Z

Pointer fungsi adalah variabel yang berisi alamat suatu fungsi. Karena ini adalah variabel penunjuk meskipun dengan beberapa properti terbatas, Anda dapat menggunakannya hampir seperti variabel penunjuk lainnya dalam struktur data.

Satu-satunya pengecualian yang dapat saya pikirkan adalah memperlakukan pointer fungsi sebagai menunjuk ke sesuatu selain nilai tunggal. Melakukan aritmatika pointer dengan menambah atau mengurangi pointer fungsi atau menambah /mengurangi offset ke pointer fungsi tidak benar-benar bermanfaat karena pointer fungsi hanya menunjuk ke satu hal, titik masuk dari suatu fungsi.

Ukuran variabel penunjuk fungsi, jumlah byte yang ditempati oleh variabel, dapat bervariasi tergantung pada arsitektur yang mendasarinya, mis. x32 atau x64 atau apa pun.

Deklarasi untuk variabel penunjuk fungsi perlu menentukan jenis informasi yang sama dengan deklarasi fungsi agar kompiler C melakukan pengecekan seperti biasanya. Jika Anda tidak menentukan daftar parameter dalam deklarasi /definisi pointer fungsi, kompiler C tidak akan dapat memeriksa penggunaan parameter. Ada kasus-kasus ketika kurangnya pemeriksaan ini bisa bermanfaat namun ingatlah bahwa jaring pengaman telah dihapus.

Beberapa contoh:

 
int func (int a, char *pStr);    // declares a function

int (*pFunc)(int a, char *pStr);  // declares or defines a function pointer

int (*pFunc2) ();                 // declares or defines a function pointer, no parameter list specified.

int (*pFunc3) (void);             // declares or defines a function pointer, no arguments.

Dua deklarasi pertama agak mirip dalam hal itu:

  •  func adalah fungsi yang mengambil int dan char * dan mengembalikan int
  •  pFunc adalah penunjuk fungsi yang diberi alamat fungsi yang mengambil int dan char * dan mengembalikan int

Jadi dari hal di atas kita bisa memiliki baris sumber di mana alamat fungsi func() ditugaskan ke variabel pointer fungsi pFunc seperti pada pFunc = func;.

Perhatikan sintaks yang digunakan dengan deklarasi /definisi pointer fungsi di mana tanda kurung digunakan untuk mengatasi aturan prioritas operator alami.

 
int *pfunc(int a, char *pStr);    // declares a function that returns int pointer
int (*pFunc)(int a, char *pStr);  // declares a function pointer that returns an int

Beberapa Contoh Penggunaan Berbeda

Beberapa contoh penggunaan pointer fungsi:

 
int (*pFunc) (int a, char *pStr);    // declare a simple function pointer variable
int (*pFunc[55])(int a, char *pStr); // declare an array of 55 function pointers
int (**pFunc)(int a, char *pStr);    // declare a pointer to a function pointer variable
struct {                             // declare a struct that contains a function pointer
    int x22;
    int (*pFunc)(int a, char *pStr);
} thing = {0, func};                 // assign values to the struct variable
char * xF (int x, int (*p)(int a, char *pStr));  // declare a function that has a function pointer as an argument
char * (*pxF) (int x, int (*p)(int a, char *pStr));  // declare a function pointer that points to a function that has a function pointer as an argument

Anda dapat menggunakan vaparameter panjang riable mencantumkan dalam definisi pointer fungsi.

 
int sum (int a, int b, ...);
int (*psum)(int a, int b, ...);

Atau Anda tidak dapat menentukan daftar parameter sama sekali. Ini bisa bermanfaat tetapi menghilangkan peluang bagi kompiler C untuk melakukan pemeriksaan pada daftar argumen yang disediakan.

 
int  sum ();      // nothing specified in the argument list so could be anything or nothing
int (*psum)();
int  sum2(void);  // void specified in the argument list so no parameters when calling this function
int (*psum2)(void);

Cast gaya C

Anda dapat menggunakan gips gaya C dengan pointer fungsi. Namun ketahuilah bahwa kompiler C mungkin lemah tentang pemeriksaan atau memberikan peringatan daripada kesalahan.

 
int sum (int a, char *b);
int (*psplsum) (int a, int b);
psplsum = sum;               // generates a compiler warning
psplsum = (int (*)(int a, int b)) sum;   // no compiler warning, cast to function pointer
psplsum = (int *(int a, int b)) sum;     // compiler error of bad cast generated, parenthesis are required.

Bandingkan Pointer Fungsi dengan Kesetaraan

Anda dapat memeriksa apakah pointer fungsi sama dengan alamat fungsi tertentu menggunakan pernyataan if meskipun saya tidak yakin seberapa berguna itu nantinya. Operator pembanding lainnya tampaknya memiliki utilitas yang lebih sedikit.

 
static int func1(int a, int b) {
    return a + b;
}

static int func2(int a, int b, char *c) {
    return c[0] + a + b;
}

static int func3(int a, int b, char *x) {
    return a + b;
}

static char *func4(int a, int b, char *c, int (*p)())
{
    if (p == func1) {
        p(a, b);
    }
    else if (p == func2) {
        p(a, b, c);      // warning C4047: '==': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
    } else if (p == func3) {
        p(a, b, c);
    }
    return c;
}

Array Pointer Fungsi

Dan jika Anda ingin memiliki array fungsi pointer masing-masing elemen di mana daftar argumen memiliki perbedaan maka Anda dapat mendefinisikan pointer fungsi dengan daftar argumen tidak ditentukan (bukan void yang berarti tidak ada argumen tetapi hanya tidak ditentukan) sesuatu seperti berikut ini meskipun Anda mungkin melihat peringatan dari kompiler C. Ini juga berfungsi untuk parameter penunjuk fungsi ke fungsi:

 
int(*p[])() = {       // an array of function pointers
    func1, func2, func3
};
int(**pp)();          // a pointer to a function pointer


p[0](a, b);
p[1](a, b, 0);
p[2](a, b);      // oops, left off the last argument but it compiles anyway.

func4(a, b, 0, func1);
func4(a, b, 0, func2);  // warning C4047: 'function': 'int (__cdecl *)()' differs in levels of indirection from 'char *(__cdecl *)(int,int,char *)'
func4(a, b, 0, func3);

    // iterate over the array elements using an array index
for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
    func4(a, b, 0, p[i]);
}
    // iterate over the array elements using a pointer
for (pp = p; pp < p + sizeof(p)/sizeof(p[0]); pp++) {
    (*pp)(a, b, 0);          // pointer to a function pointer so must dereference it.
    func4(a, b, 0, *pp);     // pointer to a function pointer so must dereference it.
}

C style namespace Menggunakan Global struct dengan Function Pointer

Anda dapat menggunakan kata kunci static untuk menentukan fungsi yang namanya ruang lingkup file dan kemudian menetapkan ini ke variabel global sebagai cara menyediakan sesuatu yang mirip dengan fungsi namespace dari C ++.

Dalam file header, tentukan struct yang akan menjadi namespace kami bersama dengan variabel global yang menggunakannya.

 
typedef struct {
   int (*func1) (int a, int b);             // pointer to function that returns an int
   char *(*func2) (int a, int b, char *c);  // pointer to function that returns a pointer
} FuncThings;

extern const FuncThings FuncThingsGlobal;

Kemudian dalam file sumber C:

 
#include "header.h"

// the function names used with these static functions do not need to be the
// same as the struct member names. It's just helpful if they are when trying
// to search for them.
// the static keyword ensures these names are file scope only and not visible
// outside of the file.
static int func1 (int a, int b)
{
    return a + b;
}

static char *func2 (int a, int b, char *c)
{
    c[0] = a % 100; c[1] = b % 50;
    return c;
}

const FuncThings FuncThingsGlobal = {func1, func2};

Ini kemudian akan digunakan dengan menentukan nama lengkap variabel struct global dan nama anggota untuk mengakses fungsi. Modifikasi const digunakan di global sehingga tidak dapat diubah secara tidak sengaja.

 
int abcd = FuncThingsGlobal.func1 (a, b);

Area Aplikasi dari Penunjuk Fungsi

Komponen pustaka DLL dapat melakukan sesuatu yang mirip dengan pendekatan C style namespace di mana antarmuka pustaka tertentu diminta dari metode pabrik di antarmuka pustaka yang mendukung pembuatan struct yang mengandung pointer fungsi .. Antarmuka pustaka ini memuat versi DLL yang diminta, membuat struct dengan pointer fungsi yang diperlukan, dan kemudian mengembalikan struct ke pemanggil yang meminta untuk digunakan.

 
typedef struct {
    HMODULE  hModule;
    int (*Func1)();
    int (*Func2)();
    int(*Func3)(int a, int b);
} LibraryFuncStruct;

int  LoadLibraryFunc LPCTSTR  dllFileName, LibraryFuncStruct *pStruct)
{
    int  retStatus = 0;   // default is an error detected

    pStruct->hModule = LoadLibrary (dllFileName);
    if (pStruct->hModule) {
        pStruct->Func1 = (int (*)()) GetProcAddress (pStruct->hModule, "Func1");
        pStruct->Func2 = (int (*)()) GetProcAddress (pStruct->hModule, "Func2");
        pStruct->Func3 = (int (*)(int a, int b)) GetProcAddress(pStruct->hModule, "Func3");
        retStatus = 1;
    }

    return retStatus;
}

void FreeLibraryFunc (LibraryFuncStruct *pStruct)
{
    if (pStruct->hModule) FreeLibrary (pStruct->hModule);
    pStruct->hModule = 0;
}

dan ini bisa digunakan seperti pada:

 
LibraryFuncStruct myLib = {0};
LoadLibraryFunc (L"library.dll", &myLib);
//  ....
myLib.Func1();
//  ....
FreeLibraryFunc (&myLib);

Pendekatan yang sama dapat digunakan untuk mendefinisikan lapisan perangkat keras abstrak untuk kode yang menggunakan model tertentu dari perangkat keras yang mendasarinya. Pointer fungsi diisi dengan fungsi spesifik perangkat keras oleh pabrik untuk menyediakan fungsionalitas khusus perangkat keras yang mengimplementasikan fungsi yang ditentukan dalam model perangkat keras abstrak. Ini dapat digunakan untuk menyediakan lapisan perangkat keras abstrak yang digunakan oleh perangkat lunak yang memanggil fungsi pabrik untuk mendapatkan antarmuka fungsi perangkat keras tertentu kemudian menggunakan pointer fungsi yang disediakan untuk melakukan tindakan untuk perangkat keras yang mendasarinya tanpa perlu mengetahui detail implementasi tentang target spesifik .

Function Pointer untuk membuat Delegasi, Penangan, dan Callbacks

Anda dapat menggunakan pointer fungsi sebagai cara untuk mendelegasikan beberapa tugas atau fungsi. Contoh klasik dalam C adalah penunjuk fungsi delegasi perbandingan yang digunakan dengan fungsi pustaka C Standar qsort() dan bsearch() untuk memberikan urutan susunan untuk menyortir daftar item atau melakukan pencarian biner pada daftar item yang diurutkan. Delegasi fungsi perbandingan menentukan algoritma pemeriksaan yang digunakan dalam pengurutan atau pencarian biner.

Penggunaan lain mirip dengan menerapkan algoritme ke wadah Pustaka Template C ++ Standar.

 
void * ApplyAlgorithm (void *pArray, size_t sizeItem, size_t nItems, int (*p)(void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for ( ; pList < pListEnd; pList += sizeItem) {
        p (pList);
    }

    return pArray;
}

int pIncrement(int *pI) {
    (*pI)++;

    return 1;
}

void * ApplyFold(void *pArray, size_t sizeItem, size_t nItems, void * pResult, int(*p)(void *, void *)) {
    unsigned char *pList = pArray;
    unsigned char *pListEnd = pList + nItems * sizeItem;
    for (; pList < pListEnd; pList += sizeItem) {
        p(pList, pResult);
    }

    return pArray;
}

int pSummation(int *pI, int *pSum) {
    (*pSum) += *pI;

    return 1;
}

// source code and then lets use our function.
int intList[30] = { 0 }, iSum = 0;

ApplyAlgorithm(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), pIncrement);
ApplyFold(intList, sizeof(int), sizeof(intList) / sizeof(intList[0]), &iSum, pSummation);

Contoh lain adalah dengan kode sumber GUI di mana handler untuk acara tertentu terdaftar dengan menyediakan pointer fungsi yang sebenarnya dipanggil ketika acara tersebut terjadi. Kerangka kerja Microsoft MFC dengan peta pesannya menggunakan sesuatu yang mirip untuk menangani pesan Windows yang dikirim ke jendela atau utas.

Fungsi asinkron yang memerlukan panggilan balik mirip dengan pengendali acara. Pengguna fungsi asinkron memanggil fungsi asinkron untuk memulai beberapa tindakan dan menyediakan penunjuk fungsi yang akan dipanggil fungsi asinkron setelah aksi selesai. Dalam hal ini acara adalah fungsi asinkron yang menyelesaikan tugasnya.

    
5
2018-08-20 05: 47: 15Z

Memulai dari awal memiliki MemoriAlamat Dari Mana Mereka Mulai Menjalankan. Dalam Bahasa Perakitan Mereka Disebut sebagai (panggil "alamat memori fungsi"). Sekarang kembali ke C Jika fungsi memiliki alamat memori maka mereka dapat dimanipulasi oleh Pointer di C.Jadi dengan aturan C

1.Pertama Anda perlu mendeklarasikan pointer agar berfungsi 2. Lewati Alamat fungsi yang Diinginkan

**** Catatan- > fungsinya harus dari jenis yang sama ****

Program Sederhana ini akan Mengilustrasikan Semua Hal.

 
#include<stdio.h>
void (*print)() ;//Declare a  Function Pointers
void sayhello();//Declare The Function Whose Address is to be passed
                //The Functions should Be of Same Type
int main()
{
 print=sayhello;//Addressof sayhello is assigned to print
 print();//print Does A call To The Function 
 return 0;
}

void sayhello()
{
 printf("\n Hello World");
}

masukkan deskripsi gambar di sini Setelah Itu mari Lihat Bagaimana mesin Memahami Mereka. Lihat petunjuk mesin dari program di atas dalam arsitektur 32 bit.

Area tanda merah menunjukkan bagaimana alamat ditukar dan disimpan dalam eax. Maka mereka adalah instruksi panggilan pada eax. eax berisi alamat fungsi yang diinginkan.

    
4
2019-06-08 06: 56: 44Z

Karena pointer fungsi sering diketik dalam panggilan balik, Anda mungkin ingin melihat di ketik panggilan balik aman . Hal yang sama berlaku untuk titik masuk, dll dari fungsi yang bukan panggilan balik.

C cukup berubah-ubah dan memaafkan pada saat yang sama :)

    
0
2009-05-09 13: 56: 12Z
sumber ditempatkan sini