Perilaku tidak terdefinisi dengan deklarasi fungsi yang tidak digunakan lagi di ANSI C


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:


/* #1 (   "foo") */ void foo(); /* #2 (  "bar") */ 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.

Source: https://habr.com/ru/post/id456682/


All Articles