
Standar ANSI C mendefinisikan konsep prototipe fungsi , yang merupakan subset dari deklarasi fungsi yang menunjukkan jenis parameter input. Prototipe diperkenalkan untuk menghilangkan kerugian yang dimiliki oleh deklarasi fungsi umum.
Jadi, menentukan daftar tipe parameter dalam tanda kurung dari prototipe fungsi adalah wajib, jika tidak, ekspresi seperti itu akan diakui oleh kompiler sebagai deklarasi fungsi usang, yang dapat menyebabkan situasi ambigu yang dijelaskan dalam artikel ini.
Prototipe usang
Deklarasi fungsi memperkenalkan tipe pengembalian fungsi dan pengenalnya ke dalam ruang lingkup yang ditentukan. Perhatikan bahwa tidak semua deklarasi fungsi dapat dianggap sebagai prototipe, tetapi hanya yang memiliki daftar tipe parameter input.
Dengan demikian, ekspresi pertama dari kode di bawah ini adalah deklarasi, tetapi bukan prototipe fungsi. Ungkapan berikut ini dapat dianggap sebagai prototipe, karena ini menentukan jenis parameternya:
void foo(); void bar(int count, const char *word);
Definisi yang sudah tidak digunakan lagi
Mari kita langsung ke tahun 1972 (tahun bahasa C dirilis) dan ingat bagaimana para programmer saat itu mendefinisikan fungsi mereka. Biarkan saya mengingatkan Anda bahwa definisi fungsi menghubungkan tanda tangannya dengan blok (tubuh) yang dapat dieksekusi. Kode ini menunjukkan definisi fungsi add
dalam gaya K&R:
void add(right, left, result) int right; int left; int *result; { *result = right + left; }
Seperti yang mungkin Anda perhatikan, dalam entri ini, tanda kurung mengidentifikasi fungsi, tetapi tidak mengandung jenis parameter input apa pun. Deklarasi fungsi "klasik" yang dijelaskan di bagian sebelumnya memiliki properti yang sama.
Situasi ambigu
Ada kemungkinan bahwa jika sintaks baru prototipe dan definisi fungsi yang diperkenalkan oleh standar ANSI C tidak diamati, situasi ambigu yang sulit dapat muncul. Pertimbangkan sebuah contoh:
#include <stdio.h> #include <stdint.h> #include <inttypes.h> #include <limits.h> /* "print_number" */ void print_number(); int main(void) { /* */ print_number((double)13.359); print_number((double)9238.46436); print_number((double)18437); /* */ print_number(UINT64_MAX); print_number("First", "Second", "Third"); print_number(NULL, "Breakfast", &print_number); } void print_number(double number) { printf(" : [%f]\n", number); }
Kami akan menganalisis program ini. Fungsi print_number
benar print_number
dideklarasikan tanpa menentukan daftar tipe parameter, sehingga Anda dapat memanggil fungsi ini dengan argumen apa pun. Program dikompilasi tanpa kesalahan dan mencetak hasil berikut:
$ gcc illegal.c -o illegal -Wall $ ./illegal : [13.359000] : [9238.464360] : [18437.000000] : [0.000000] : [0.000000] : [0.000000]
Catat juga bahwa bahkan dengan flag -Wall
, kompiler -Wall
gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
tidak menghasilkan peringatan apa pun (tapi itu akan sangat diinginkan).
Memperbaiki program ini tidak akan sulit, cukup tambahkan angka double number
dalam tanda kurung deklarasi fungsi print_number
pada baris ketujuh, setelah itu setiap kompiler yang mengikuti standar akan menunjukkan kesalahan dalam fungsi main()
:
$ gcc -Wall illegal.c -o illegal illegal.c: In function 'main': illegal.c:17:22: error: incompatible type for argument 1 of 'print_number' print_number("First", "Second", "Third"); ^~~~~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'char *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:17:9: error: too many arguments to function 'print_number' print_number("First", "Second", "Third"); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:22: error: incompatible type for argument 1 of 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'void *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:9: error: too many arguments to function 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~
Fungsi tanpa parameter
Saya juga mencatat bahwa menentukan kata kunci void
dalam tanda kurung prototipe dan definisi fungsi yang tidak menggunakan parameter sangat diinginkan (tetapi tidak perlu). Jika aturan ini tidak diperhatikan, kompiler tidak akan dapat memeriksa korespondensi dari tipe argumen yang diteruskan ketika fungsi dipanggil dengan tipe yang valid dari definisi.
#include <stdio.h> /* "do_something" */ void do_something(); int main(void) { /* "do_something" */ do_something(NULL, "Papa Johns", 2842, 1484.3355); } void do_something() { puts("I am doing something interesting right now!"); }
Kode di atas harus diperbaiki dengan memasukkan kata kunci void
dalam definisi dan deklarasi fungsi do_something()
, jika tidak, program ini akan dikompilasi tanpa kesalahan. Dalam contoh ini, fungsi main()
juga didefinisikan dengan token void
di parameter, meskipun ini tidak perlu.
Kesimpulan
Penulisan artikel ini terinspirasi oleh buku Stephen Prat "bahasa pemrograman C. Kuliah dan latihan. Edisi keenam", dan khususnya bagian "Fungsi dengan argumen" dari bab kelima.