Mengapa Anda dapat memiliki definisi metode di dalam file header di C ++ ketika di C Anda tidak bisa?

23

Di C, Anda tidak dapat memiliki definisi fungsi / implementasi di dalam file header. Namun, di C ++ Anda dapat memiliki implementasi metode lengkap di dalam file header. Mengapa perilakunya berbeda?

Joshua Partogi
sumber

Jawaban:

28

Di C, jika Anda menentukan fungsi dalam file header, maka fungsi itu akan muncul di setiap modul yang dikompilasi yang menyertakan file header itu, dan simbol publik akan diekspor untuk fungsi tersebut. Jadi jika fungsi additup didefinisikan dalam header.h, dan foo.c dan bar.c keduanya menyertakan header.h, maka foo.o dan bar.o keduanya akan menyertakan salinan additup.

Ketika Anda pergi untuk menautkan kedua file objek tersebut secara bersamaan, linker akan melihat bahwa penambahan simbol didefinisikan lebih dari sekali, dan tidak akan mengizinkannya.

Jika Anda mendeklarasikan fungsi sebagai statis, maka tidak ada simbol yang akan diekspor. File objek foo.o dan bar.o akan tetap berisi salinan terpisah dari kode untuk fungsi tersebut, dan mereka akan dapat menggunakannya, tetapi linker tidak akan dapat melihat salinan fungsi apa pun, sehingga tidak akan mengeluh. Tentu saja, tidak ada modul lain yang dapat melihat fungsinya. Dan program Anda akan penuh dengan dua salinan identik dengan fungsi yang sama.

Jika Anda hanya mendeklarasikan fungsi dalam file header, tetapi tidak mendefinisikannya, dan kemudian mendefinisikannya hanya dalam satu modul, maka linker akan melihat satu salinan dari fungsi, dan setiap modul dalam program Anda akan dapat melihatnya dan Gunakan. Dan program Anda yang dikompilasi akan berisi hanya satu salinan fungsi.

Jadi, Anda dapat memiliki definisi fungsi di file header di C, itu hanya gaya buruk, bentuk buruk, dan semua ide buruk.

(Dengan "menyatakan", maksud saya menyediakan prototipe fungsi tanpa badan; dengan "mendefinisikan" Maksud saya menyediakan kode aktual dari badan fungsi; ini adalah terminologi standar C.)

David Conrad
sumber
2
Ini bukan ide yang buruk - hal-hal semacam ini dapat ditemukan bahkan di header GNU Libc.
SK-logic
tapi bagaimana dengan pembungkus idiomatik dari file header dalam arahan kompilasi bersyarat? Kemudian, bahkan dengan fungsi dinyatakan DAN didefinisikan dalam header, itu hanya akan dimuat sekali. Saya baru mengenal C, jadi saya mungkin salah paham.
user305964
2
@apiapi Masalahnya adalah, bahwa pembungkus hanya melindungi selama menjalankan tunggal dari kompiler. Jadi, jika foo.c dikompilasi ke foo.o dalam sekali jalan, bar.c ke bar.o di jalur lain, dan foo.o dan bar.o ditautkan ke a.out dalam sepertiga (seperti biasa), yang membungkus tidak mencegah banyak kejadian, satu di setiap file objek.
David Conrad
Bukankah masalah yang dijelaskan di sini #ifndef HEADER_Hadalah apa yang seharusnya dicegah?
Robert Harvey
27

C dan C ++ berperilaku sangat mirip dalam hal ini - Anda dapat memiliki inlinefungsi dalam header. Dalam C ++, metode apa pun yang tubuhnya berada di dalam definisi kelas secara implisit inline. Jika Anda ingin melakukan hal yang sama dalam C, nyatakan fungsinya static inline.

Simon Richter
sumber
" mendeklarasikan fungsistatic inline " ... dan Anda masih akan memiliki banyak salinan fungsi di setiap unit terjemahan yang menggunakannya. Dalam C ++ dengan tidak static inlineberfungsi Anda hanya akan memiliki satu salinan. Untuk benar-benar memiliki implementasi di header di C, Anda harus 1) menandai implementasi sebagai inline(misalnya inline void func(){do_something();}), dan 2) benar-benar mengatakan bahwa fungsi ini akan berada di beberapa unit terjemahan tertentu (misalnya void func();).
Ruslan
6

Konsep file header perlu sedikit penjelasan:

Entah Anda memberikan file pada baris perintah kompiler, atau melakukan '#include'. Sebagian besar kompiler menerima file perintah dengan ekstensi c, C, cpp, c ++, dll. Sebagai file sumber. Namun, mereka biasanya menyertakan opsi baris perintah untuk memungkinkan penggunaan ekstensi sembarang ke file sumber juga.

Umumnya file yang diberikan pada baris perintah disebut 'Sumber', dan yang disertakan disebut 'Header'.

Langkah preprocessor benar-benar mengambil semuanya dan membuat semuanya tampak seperti satu file besar ke kompiler. Apa yang ada di header atau di sumber sebenarnya tidak relevan pada saat ini. Biasanya ada opsi kompiler yang dapat menunjukkan output dari tahap ini.

Jadi untuk setiap file yang diberikan pada baris perintah kompiler, file besar diberikan kepada kompiler. Ini mungkin memiliki kode / data yang akan menempati memori dan / atau membuat simbol untuk dirujuk dari file lain. Sekarang masing-masing akan menghasilkan gambar 'objek'. Linker dapat memberikan 'simbol duplikat' jika simbol yang sama ditemukan di lebih dari dua file objek yang sedang dihubungkan bersama. Mungkin ini alasannya; tidak disarankan untuk meletakkan kode di file header, yang dapat membuat simbol di file objek.

'Inline' biasanya inline .. tetapi ketika debugging mereka mungkin tidak inline. Jadi mengapa linker tidak memberikan kesalahan yang didefinisikan multiply? Sederhana ... Ini adalah simbol 'lemah', dan selama semua data / kode untuk simbol lemah dari semua objek memiliki ukuran dan konten yang sama, tautan tersebut akan menyimpan satu salinan dan menjatuhkan salinan dari objek lain. Berhasil.

vrdhn
sumber
3

Anda dapat melakukan ini di C99: inlinefungsi dijamin disediakan di tempat lain, jadi jika suatu fungsi tidak diuraikan, definisinya diterjemahkan ke dalam deklarasi (yaitu, implementasi dibuang). Dan, tentu saja, bisa Anda gunakan static.

Logika SK
sumber
1

C ++ kutipan standar

The C ++ 17 N4659 standar rancangan 10.1.6 "The inline specifier" mengatakan bahwa metode yang secara implisit inline:

4 Fungsi yang didefinisikan dalam definisi kelas adalah fungsi inline.

dan kemudian lebih jauh ke bawah kita melihat bahwa metode sebaris tidak hanya bisa, tetapi harus didefinisikan pada semua unit terjemahan:

6 Fungsi atau variabel sebaris harus didefinisikan di setiap unit terjemahan yang digunakan odr dan harus memiliki definisi yang persis sama dalam setiap kasus (6.2).

Ini juga secara eksplisit disebutkan dalam catatan di 12.2.1 "Fungsi anggota":

1 Fungsi anggota dapat didefinisikan (11.4) dalam definisi kelasnya, dalam hal ini fungsi anggota sebaris (10.1.6) [...]

3 [Catatan: Paling tidak ada satu definisi fungsi anggota non-inline dalam suatu program. Mungkin ada lebih dari satu definisi fungsi anggota inline dalam suatu program. Lihat 6.2 dan 10.1.6. - catatan akhir]

Implementasi GCC 8.3

main.cpp

struct MyClass {
    void myMethod() {}
};

int main() {
    MyClass().myMethod();
}

Kompilasi dan lihat simbol:

g++ -c main.cpp
nm -C main.o

keluaran:

                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
                 U __stack_chk_fail
0000000000000000 T main

kemudian kita melihat man nmbahwa MyClass::myMethodsimbol ditandai sebagai lemah pada file objek ELF, yang menyiratkan bahwa itu dapat muncul pada beberapa file objek:

"W" "w" Simbol adalah simbol lemah yang belum secara khusus ditandai sebagai simbol objek lemah. Ketika simbol yang didefinisikan lemah dikaitkan dengan simbol yang didefinisikan normal, simbol yang didefinisikan normal digunakan tanpa kesalahan. Ketika simbol tidak terdefinisi lemah dihubungkan dan simbol tidak didefinisikan, nilai simbol ditentukan dengan cara sistem spesifik tanpa kesalahan. Pada beberapa sistem, huruf besar menunjukkan bahwa nilai default telah ditentukan.

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
sumber
-4

Mungkin karena alasan yang sama bahwa Anda harus meletakkan implementasi metode lengkap di dalam definisi kelas di Jawa.

Mereka mungkin terlihat serupa, dengan kurung berlekuk-lekuk dan banyak kata kunci yang sama, tetapi mereka bahasa yang berbeda.

Paul Butcher
sumber
1
Tidak, sebenarnya ada jawaban nyata, dan salah satu tujuan utama C ++ adalah untuk kompatibel dengan C.
Ed S.
4
Tidak, itu dirancang untuk memiliki "Tingkat kompatibilitas C yang tinggi", dan "Tidak ada kompatibilitas serampangan dengan C". (keduanya dari Stroustrup). Saya setuju bahwa jawaban yang lebih mendalam dapat diberikan, untuk menyoroti mengapa ketidakcocokan khusus ini tidak serampangan. Jangan ragu untuk menyediakannya.
Paul Butcher
Saya akan melakukannya, tetapi Simon Richter sudah memiliki ketika saya memposting itu. Kami dapat berdebat tentang perbedaan antara "kompatibel dengan backward" dan "Tingkat C kompatibilitas yang tinggi", tetapi kenyataannya tetap bahwa jawaban ini salah. Pernyataan terakhir akan benar jika kita membandingkan C # dan C ++, tetapi tidak terlalu banyak dengan C dan C ++.
Ed S.