Mengapa suatu fungsi tanpa parameter (dibandingkan dengan definisi fungsi sebenarnya) dikompilasi?

388

Saya baru saja menemukan kode C seseorang yang saya bingung mengapa kompilasi. Ada dua hal yang saya tidak mengerti.

Pertama, prototipe fungsi tidak memiliki parameter dibandingkan dengan definisi fungsi yang sebenarnya. Kedua, parameter dalam definisi fungsi tidak memiliki tipe.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

Mengapa ini bekerja? Saya telah mengujinya di beberapa kompiler, dan itu berfungsi dengan baik.

AdmiralJonB
sumber
77
Ini K&R C. Kami menulis kode seperti ini pada 1980-an sebelum ada prototipe fungsi penuh.
hughdbrown
6
gcc tidak memperingatkan -Wstrict-prototypesuntuk int func()dan int main(): xc: 3: peringatan: deklarasi fungsi bukan prototipe. Anda harus menyatakan main()sebagai main(void)juga.
Jens
3
@ Jens Mengapa Anda mengedit pertanyaan? Anda sepertinya telah melewatkan intinya ...
dlras2
1
Saya baru saja membuat int implisit eksplisit. Bagaimana hal itu melewatkan intinya? Saya percaya intinya adalah mengapa int func();kompatibel int func(arglist) { ... }.
Jens
3
@ MatPetersson Ini salah. C99 5.1.2.2.1 secara tegas bertentangan dengan klaim Anda dan menyatakan bahwa versi no argumen adalah int main(void).
Jens

Jawaban:

271

Semua jawaban lainnya benar, tetapi hanya untuk penyelesaian

Suatu fungsi dideklarasikan dengan cara berikut:

  return-type function-name(parameter-list,...) { body... }

tipe-kembali adalah tipe variabel yang fungsi kembali. Ini tidak bisa menjadi tipe array atau tipe fungsi. Jika tidak diberikan, maka int diasumsikan .

function-name adalah nama fungsi.

parameter-list adalah daftar parameter yang fungsinya dipisahkan oleh koma. Jika tidak ada parameter yang diberikan, maka fungsi tidak mengambil apa pun dan harus didefinisikan dengan seperangkat tanda kurung kosong atau dengan kata kunci batal. Jika tidak ada tipe variabel di depan variabel dalam daftar paramater, maka int diasumsikan . Array dan fungsi tidak diteruskan ke fungsi, tetapi secara otomatis dikonversi ke pointer. Jika daftar diakhiri dengan elipsis (, ...), maka tidak ada jumlah parameter yang ditetapkan. Catatan: header stdarg.h dapat digunakan untuk mengakses argumen saat menggunakan elipsis.

Dan lagi demi kelengkapan. Dari spesifikasi C11 6: 11: 6 (halaman: 179)

The Penggunaan declarators fungsi dengan tanda kurung kosong (tidak prototipe format jenis parameter declarators) adalah fitur usang .

Krishnabhadra
sumber
2
"Jika tidak ada tipe variabel di depan variabel dalam daftar parameter, maka int diasumsikan." Saya melihat ini di tautan yang Anda berikan, tetapi saya tidak dapat menemukannya di standar c89, c99 ... Bisakah Anda memberikan sumber lain?
godaygo
"Jika tidak ada parameter yang diberikan, maka fungsi tidak mengambil apa pun dan harus didefinisikan dengan seperangkat tanda kurung kosong" kedengarannya bertentangan dengan jawaban Tony The Lion, tetapi saya baru tahu bahwa jawaban Tony The Lion benar.
jakun
160

Dalam C func()berarti Anda dapat melewati sejumlah argumen. Jika Anda tidak ingin argumen maka Anda harus menyatakan sebagai func(void). Jenis yang Anda berikan ke fungsi Anda, jika tidak ditentukan secara default int.

Tony The Lion
sumber
3
Sebenarnya tidak ada satu tipe default ke int. Bahkan, semua argumen default ke int (bahkan tipe pengembalian). Tidak apa-apa untuk menelepon func(42,0x42);(di mana semua panggilan harus menggunakan dua bentuk args).
Jens
1
Dalam C sangat umum untuk tipe yang tidak ditentukan untuk default ke int seperti misalnya ketika Anda mendeklarasikan variabel: unsigned x; Apa jenis variabel x? Ternyata int
bitek yang
4
Saya lebih suka mengatakan int implisit sudah umum di masa lalu. Jelas tidak lagi dan pada C99 dihapus dari C.
Jens
2
Mungkin perlu dicatat bahwa jawaban ini berlaku untuk prototipe fungsi dengan daftar parameter kosong, tetapi tidak definisi fungsi dengan daftar parameter kosong.
autis
@mnemonicflow itu bukan "tipe yang tidak ditentukan, default ke int"; unsignedhanyalah nama lain untuk tipe yang sama dengan unsigned int.
hobbs
58

int func();adalah deklarasi fungsi usang dari hari-hari ketika tidak ada standar C, yaitu hari-hari K&R C (sebelum 1989, tahun standar "ANSI C" pertama diterbitkan).

Ingat bahwa tidak ada prototipe dalam K&R C dan kata kunci voidbelum ditemukan. Yang bisa Anda lakukan adalah memberi tahu kompiler tentang jenis kembali fungsi. Daftar parameter kosong dalam K&R C berarti jumlah argumen yang "tidak ditentukan tapi tetap". Tetap berarti bahwa Anda harus memanggil fungsi dengan sama jumlah args setiap kali (sebagai lawan dari variadic fungsi seperti printf, di mana jumlah dan jenis dapat bervariasi untuk setiap panggilan).

Banyak kompiler akan mendiagnosis konstruk ini; khususnya gcc -Wstrict-prototypesakan memberi tahu Anda "deklarasi fungsi bukan prototipe", yang tepat, karena terlihat seperti prototipe (terutama jika Anda diracuni oleh C ++!), tetapi tidak. Ini adalah deklarasi tipe pengembalian K&R gaya lama.

Aturan praktis: Jangan pernah biarkan deklarasi daftar parameter kosong, gunakan int func(void)untuk lebih spesifik. Ini mengubah deklarasi tipe pengembalian K&R menjadi prototipe C89 yang tepat. Kompiler senang, pengembang senang, dam statis senang. Mereka yang disesatkan oleh ^ W ^ Wfond dari C ++ mungkin merasa ngeri, karena mereka perlu mengetikkan karakter tambahan ketika mereka mencoba melatih kemampuan bahasa asing mereka :-)

Jens
sumber
1
Tolong jangan hubungkan ini dengan C ++. Adalah masuk akal bahwa daftar argumen kosong berarti tidak ada parameter , dan orang-orang di dunia tidak tertarik untuk berurusan dengan kesalahan yang dilakukan oleh K&R guys sekitar 40 tahun yang lalu. Tapi para pakar komite terus menyeret pilihan kontranatural - ke C99, C11.
pfalcon
1
@pfalcon Akal sehat cukup subyektif. Apa yang masuk akal bagi satu orang adalah kegilaan yang jelas bagi orang lain. Pemrogram C profesional tahu bahwa daftar parameter kosong menunjukkan sejumlah argumen yang tidak ditentukan tetapi tetap. Mengubah itu kemungkinan akan merusak banyak implementasi. Menyalahkan komite untuk menghindari perubahan diam adalah menggonggong pohon yang salah, IMHO.
Jens
53
  • Daftar parameter kosong berarti "argumen apa pun", sehingga definisi tersebut tidak salah.
  • Jenis yang hilang diasumsikan int.

Saya akan menganggap bangunan apa pun yang melewati ini kurang dalam tingkat peringatan / kesalahan yang dikonfigurasi, tidak ada gunanya menjadi ini memungkinkan untuk kode aktual.

beristirahat
sumber
30

Ini adalah deklarasi dan definisi fungsi gaya K&R . Dari C99 Standard (ISO / IEC 9899: TC3)

Bagian 6.7.5.3 Deklarator Fungsi (termasuk prototipe)

Daftar pengidentifikasi hanya menyatakan pengidentifikasi parameter fungsi. Daftar kosong di deklarator fungsi yang merupakan bagian dari definisi fungsi yang menentukan bahwa fungsi tidak memiliki parameter. Daftar kosong dalam deklarator fungsi yang bukan bagian dari definisi fungsi itu menentukan bahwa tidak ada informasi tentang jumlah atau jenis parameter yang disediakan. (Jika kedua tipe fungsi adalah "gaya lama", tipe parameter tidak dibandingkan.)

Bagian 6.11.6 Deklarator fungsi

Penggunaan deklarator fungsi dengan tanda kurung kosong (bukan deklarator tipe parameter format prototipe) adalah fitur usang.

Bagian 6.11.7 Definisi fungsi

Penggunaan definisi fungsi dengan pengidentifikasi parameter dan daftar deklarasi yang terpisah (bukan tipe parameter format prototipe dan deklarator pengidentifikasi) adalah fitur usang.

Yang gaya lama berarti K & R gaya

Contoh:

Pernyataan: int old_style();

Definisi:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}
Lei Mou
sumber
16

C mengasumsikan intjika tidak ada tipe yang diberikan pada tipe pengembalian fungsi dan daftar parameter . Hanya untuk aturan ini mengikuti hal-hal aneh yang dimungkinkan.

Definisi fungsi terlihat seperti ini.

int func(int param) { /* body */}

Jika ini prototipe yang Anda tulis

int func(int param);

Dalam prototipe Anda hanya dapat menentukan jenis parameter. Nama parameter tidak wajib. Begitu

int func(int);

Juga jika Anda tidak menentukan tipe parameter tetapi nama intdiasumsikan sebagai tipe.

int func(param);

Jika Anda melangkah lebih jauh, mengikuti pekerjaan juga.

func();

Compiler mengasumsikan int func()ketika Anda menulis func(). Tapi jangan dimasukkan ke func()dalam tubuh fungsi. Itu akan menjadi panggilan fungsi

Shiplu Mokaddim
sumber
3
Daftar parameter kosong tidak terkait dengan tipe int implisit. int func()bukan bentuk implisit dari int func(int).
John Bartholomew
11

Seperti yang dinyatakan @ Krishnabhadra, semua tanggapan sebelumnya dari pengguna lain, memiliki interpretasi yang benar, dan saya hanya ingin membuat analisis yang lebih rinci dari beberapa poin.

Dalam Old-C seperti dalam ANSI-C " parameter formal yang tidak diketik", ambil dimedion dari register kerja Anda atau kemampuan kedalaman instruksi (shadow register atau instruksi siklus kumulatif), dalam 8bit MPU, akan menjadi int16, dalam 16bit MPU dan seterusnya akan menjadi int16 dan seterusnya, dalam hal arsitektur 64bit dapat memilih untuk mengkompilasi opsi seperti: -m32.

Meskipun tampaknya implementasi lebih mudah pada level tinggi, Untuk melewati beberapa parameter, pekerjaan programmer dalam langkah kontrol tipe data dimencion, menjadi lebih banyak menuntut.

Dalam kasus lain, untuk beberapa arsitektur mikroprosesor, kompiler ANSI menyesuaikan, memanfaatkan beberapa fitur lama ini untuk mengoptimalkan penggunaan kode, memaksa lokasi "parameter formal yang tidak diketik" ini untuk bekerja di dalam atau di luar register kerja, hari ini Anda dapat hampir sama dengan penggunaan "volatile" dan "register".

Tetapi harus dicatat bahwa kompiler paling modern, tidak membuat perbedaan antara dua jenis pernyataan parameter.

Contoh kompilasi dengan gcc di linux:

main.c

main2.c

main3.c  
Bagaimanapun pernyataan prototipe secara lokal tidak ada gunanya, karena tidak ada panggilan tanpa parameter referensi ke prototipe ini akan lalai. Jika Anda menggunakan sistem dengan "parameter formal yang tidak diketik", untuk panggilan eksternal, lanjutkan untuk menghasilkan tipe data prototipe deklaratif.

Seperti ini:

int myfunc(int param);
RTOSkit
sumber
5
Saya telah mengambil kesulitan untuk mengoptimalkan gambar untuk ukuran dalam byte gambar, adalah seminimal mungkin. Saya pikir gambar bisa mendapatkan pandangan cepat tentang kurangnya perbedaan dalam kode yang dihasilkan. Saya telah menguji kode, menghasilkan dokumentasi nyata dari apa yang diklaimnya dan bukan hanya berteori tentang masalah tersebut. tetapi tidak semua melihat dunia dengan cara yang sama. Saya sangat menyesal bahwa upaya saya untuk memberikan lebih banyak informasi kepada masyarakat telah sangat mengganggu Anda.
RTOSkit
1
Saya cukup yakin bahwa tipe pengembalian tidak ditentukan selalu int, yang umumnya ukuran register kerja atau 16 bit, mana yang lebih kecil.
supercat
@supercat +1 Pengurangan sempurna, Anda benar! Inilah yang saya bahas di atas, kompiler yang dirancang secara default untuk arsitektur yang ditentukan, selalu mencerminkan ukuran register kerja CPU / MPU yang bersangkutan, Jadi dalam lingkungan yang tertanam, ada berbagai strategi pengetikan ulang pada suatu OS waktu nyata, ke dalam lapisan kompiler (stdint.h), atau ke dalam lapisan portabilitas, yang menjadikan portabel OS yang sama di banyak arsitektur lainnya, dan diperoleh dengan menyelaraskan jenis-jenis khusus CPU / MPU (int, panjang, panjang, dll) dengan tipe sistem generik u8, u16, u32.
RTOSkit
@supercat Tanpa tipe kontrol yang ditawarkan oleh sistem operasi atau oleh kompiler tertentu, Anda perlu sedikit perhatian dalam waktu pengembangan, dan, Anda membuat semua "tugas yang tidak diketik" selaras dengan desain aplikasi Anda, sebelum mendapatkan kejutan menemukan " 16bit int "dan bukan" 32bit int ".
RTOSkit
@RTOSkit: Jawaban Anda menunjukkan bahwa tipe default pada prosesor 8-bit adalah Int8. Saya ingat beberapa kompiler C-ish untuk arsitektur merek PICmicro di mana itu terjadi, tapi saya tidak berpikir apa pun yang menyerupai standar C pernah memungkinkan intjenis yang tidak mampu memegang semua nilai dalam kisaran - 32767 hingga +32767 (note -32768 tidak diperlukan) dan juga mampu menampung semua charnilai (artinya jika char16 bit, harus ditandatangani atau intharus lebih besar).
supercat
5

Mengenai tipe parameter, sudah ada jawaban yang benar di sini tetapi jika Anda ingin mendengarnya dari kompiler, Anda dapat mencoba menambahkan beberapa flag (flag hampir selalu merupakan ide yang bagus).

kompilasi program Anda menggunakan gcc foo.c -Wextrasaya dapatkan:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

anehnya -Wextratidak menangkap ini karena clang(itu tidak mengenali -Wmissing-parameter-typekarena alasan tertentu, mungkin untuk yang historis yang disebutkan di atas) tetapi -pedantictidak:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

Dan untuk masalah prototipe seperti dikatakan di atas int func()mengacu pada parameter arbitrer kecuali Anda secara khusus mendefinisikannya sebagai int func(void)yang kemudian akan memberi Anda kesalahan seperti yang diharapkan:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

atau clangsebagai:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.
tidak ada
sumber
3

Jika deklarasi fungsi tidak memiliki parameter yaitu kosong maka itu mengambil jumlah argumen yang tidak ditentukan. Jika Anda ingin membuatnya tidak perlu argumen maka ubah ke:

int func(void);
PP
sumber
1
"Jika prototipe fungsi tidak memiliki parameter" Itu bukan prototipe fungsi, hanya deklarasi fungsi.
effeffe
@effeffe memperbaikinya. Saya tidak menyadari pertanyaan ini akan mendapat banyak perhatian;)
PP
3
Bukankah "prototipe fungsi" hanya ekspresi alternatif (mungkin kuno) untuk "deklarasi fungsi"?
Giorgio
@Giorgio IMO, keduanya benar. "prototipe fungsi" harus cocok dengan "definisi fungsi". Saya mungkin berpikir effeffe berarti deklarasi dalam pertanyaan dan apa yang saya maksudkan adalah dalam jawaban saya.
PP
@ Giorgio no, deklarasi fungsi dibuat oleh nilai tipe kembali, pengidentifikasi dan daftar parameter opsional ; prototipe fungsi adalah deklarasi fungsi dengan daftar parameter.
effeffe
0

Inilah sebabnya saya biasanya menyarankan orang untuk mengkompilasi kode mereka dengan:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Bendera ini memberlakukan beberapa hal:

  • -Wmissing-variable-declarations: Tidak mungkin untuk mendeklarasikan fungsi non-statis tanpa mendapatkan prototipe terlebih dahulu. Ini membuatnya lebih mungkin bahwa prototipe dalam file header cocok dengan definisi aktual. Atau, itu memaksa Anda menambahkan kata kunci statis ke fungsi yang tidak perlu terlihat secara publik.
  • -Wstrict-variable-declarations: Prototipe harus mencantumkan argumen dengan benar.
  • -Wold-style-definition: Definisi fungsi itu sendiri juga harus mencantumkan argumen dengan benar.

Bendera ini juga digunakan secara default di banyak proyek Open Source. Sebagai contoh, FreeBSD mengaktifkan flag-flag ini ketika membangun dengan WARNS = 6 di Makefile Anda.

Ed Schouten
sumber