Mengapa beberapa program C ditulis dalam satu file sumber besar?

88

Sebagai contoh, alat SysInternals "FileMon" dari masa lalu memiliki driver mode kernel yang kode sumbernya seluruhnya dalam satu file 4.000-baris. Hal yang sama untuk program ping pertama yang pernah ditulis (~ 2.000 LOC).

Dedak
sumber

Jawaban:

143

Menggunakan banyak file selalu memerlukan overhead administratif tambahan. Kita harus menyiapkan skrip build dan / atau makefile dengan tahapan kompilasi dan penautan yang terpisah, pastikan dependensi antara file yang berbeda dikelola dengan benar, menulis skrip "zip" untuk distribusi kode sumber yang lebih mudah melalui email atau unduh, dan sebagainya di. IDE modern saat ini biasanya mengambil banyak beban itu, tetapi saya cukup yakin pada saat program ping pertama kali ditulis, tidak ada IDE yang tersedia. Dan untuk file sekecil ~ 4000 LOC, tanpa IDE yang mengelola banyak file untuk Anda dengan baik, pertukaran antara overhead yang disebutkan dan manfaat dari menggunakan banyak file mungkin memungkinkan orang membuat keputusan untuk pendekatan file tunggal.

Doc Brown
sumber
9
"Dan untuk file sekecil ~ 4000 LOC ..." Aku sedang bekerja sebagai JS dev sekarang. Ketika saya memiliki file yang panjangnya hanya 400 baris kode, saya merasa gugup tentang seberapa besar ukurannya! (Tapi kami punya lusinan file dalam proyek kami.)
Kevin
36
@ Kevin: satu rambut di kepala saya terlalu sedikit, satu rambut di sup saya terlalu banyak ;-) AFAIK di JS banyak file tidak menyebabkan banyak biaya administrasi seperti pada "C tanpa IDE modern".
Doc Brown
4
@Kevin JS adalah binatang yang sangat berbeda. JS dikirimkan ke pengguna akhir setiap kali pengguna memuat situs web dan belum memilikinya di-cache oleh browser mereka. C hanya harus memiliki kode yang ditransmisikan sekali, maka orang di ujung lain mengkompilasinya dan tetap dikompilasi (jelas ada pengecualian, tapi itu kasus penggunaan umum yang diharapkan). Juga hal-hal C cenderung kode warisan, seperti banyak dari proyek '4000 baris adalah normal' yang dijelaskan orang dalam komentar.
Pharap
5
@Kevin Sekarang buka dan lihat bagaimana underscore.js (1700 loc, satu file) dan segudang perpustakaan lain yang didistribusikan ditulis. Javascript sebenarnya hampir sama buruknya dengan C sehubungan dengan modularisasi dan penyebaran.
Voo
2
@Pharap Saya pikir dia bermaksud menggunakan sesuatu seperti Webpack sebelum menyebarkan kode. Dengan Webpack, Anda dapat mengerjakan beberapa file dan kemudian mengompilasinya menjadi satu bundel.
Brian McCutchon
81

Karena C tidak pandai modularisasi. Itu menjadi berantakan (file header dan #include, fungsi extern, kesalahan waktu tautan, dll) dan semakin banyak modul yang Anda bawa, semakin sulit hasilnya.

Bahasa yang lebih modern memiliki kemampuan modularisasi yang lebih baik sebagian karena mereka belajar dari kesalahan C, dan mereka membuatnya lebih mudah untuk memecah basis kode Anda menjadi unit yang lebih kecil, lebih sederhana. Tetapi dengan C, akan bermanfaat untuk menghindari atau meminimalkan semua masalah itu, bahkan jika itu berarti menyamakan kode yang dianggap terlalu banyak ke dalam satu file.

Mason Wheeler
sumber
38
Saya pikir tidak adil untuk menggambarkan pendekatan C sebagai 'kesalahan'; mereka adalah keputusan yang masuk akal dan masuk akal pada saat mereka dibuat.
Jack Aidley
14
Tak satu pun dari hal-hal modularisasi yang sangat rumit. Itu bisa dibuat rumit oleh gaya pengkodean yang buruk, tetapi tidak sulit untuk dipahami atau diimplementasikan, dan tidak ada yang bisa digolongkan sebagai "kesalahan". Alasan sebenarnya, sesuai jawaban Snowman, adalah bahwa optimasi atas beberapa file sumber tidak begitu baik di masa lalu, dan bahwa driver FileMon membutuhkan kinerja tinggi. Juga, bertentangan dengan opini OP, itu bukan file besar.
Graham
8
@Graham File apa pun yang lebih besar dari 1000 baris kode harus diperlakukan sebagai bau kode.
Mason Wheeler
11
@JackAidley itu tidak adil sama sekali , memiliki sesuatu menjadi kesalahan tidak saling eksklusif dengan mengatakan itu adalah keputusan yang masuk akal pada saat itu. Kesalahan tidak dapat dihindari karena informasi yang tidak sempurna dan waktu yang terbatas dan harus dipelajari dari tidak disembunyikan atau direklasifikasi untuk menyelamatkan muka.
Jared Smith
8
Siapa pun yang mengklaim bahwa pendekatan C bukan kesalahan, gagal memahami bagaimana file C yang tampaknya sepuluh-liner dapat benar-benar menjadi file sepuluh-ribu-liner dengan semua header #include: d. Ini berarti setiap file dalam proyek Anda secara efektif setidaknya sepuluh ribu baris, tidak peduli berapa banyak jumlah baris yang diberikan oleh "wc-l". Dukungan yang lebih baik untuk modularitas akan dengan mudah memotong waktu parsing dan kompilasi menjadi sebagian kecil.
juhist
37

Selain alasan historis, ada satu alasan untuk menggunakan ini dalam perangkat lunak sensitif kinerja modern. Ketika semua kode berada dalam satu unit kompilasi, kompiler dapat melakukan optimasi seluruh program. Dengan unit kompilasi terpisah, kompiler tidak dapat mengoptimalkan seluruh program dengan cara tertentu (misalnya inlining kode tertentu).

Linker tentu saja dapat melakukan beberapa optimasi selain apa yang dapat dilakukan oleh kompiler, tetapi tidak semua. Sebagai contoh: penghubung modern sangat bagus dalam menghilangkan fungsi yang tidak direferensikan, bahkan di banyak file objek. Mereka mungkin dapat melakukan beberapa optimasi lain, tetapi tidak seperti apa yang dapat dilakukan oleh kompiler di dalam suatu fungsi.

Salah satu contoh modul kode sumber tunggal yang terkenal adalah SQLite. Anda dapat membaca lebih lanjut tentang ini di halaman SQLite Amalgamation .

1. Ringkasan Eksekutif

Lebih dari 100 file sumber terpisah digabungkan menjadi satu file besar tunggal kode-C bernama "sqlite3.c" dan disebut "penggabungan". Penggabungan ini berisi semua yang dibutuhkan aplikasi untuk menanamkan SQLite. File amalgamasi memiliki panjang lebih dari 180.000 baris dan lebih dari 6 megabita.

Menggabungkan semua kode untuk SQLite ke dalam satu file besar membuat SQLite lebih mudah untuk digunakan - hanya ada satu file untuk dilacak. Dan karena semua kode berada dalam unit terjemahan tunggal, kompiler dapat melakukan optimasi antar-prosedur yang lebih baik sehingga menghasilkan kode mesin yang antara 5% dan 10% lebih cepat.


sumber
15
Tetapi perhatikan bahwa kompiler C modern dapat melakukan optimalisasi seluruh program dari beberapa file sumber (walaupun tidak jika Anda mengompilasinya menjadi file objek individual terlebih dahulu).
Davislor
10
@ Davidvis Lihat skrip build tipikal: kompiler tidak realistis akan melakukan itu.
4
Secara signifikan lebih mudah untuk mengubah skrip build $(CC) $(CFLAGS) $(LDFLAGS) -o $(TARGET) $(CFILES)daripada memindahkan semuanya ke satu file soudce. Anda bahkan dapat melakukan kompilasi seluruh program sebagai target alternatif untuk skrip build tradisional yang melewatkan kompilasi ulang file sumber yang tidak berubah, mirip dengan cara orang mematikan profil dan debugging untuk target produksi. Anda tidak memiliki opsi itu jika semuanya berada dalam satu tumpukan besar sumber daya. Bukan seperti yang biasa dilakukan orang, tetapi tidak ada yang merepotkan.
Davislor
9
@Davislor seluruh pengoptimalan program / optimasi waktu tautan (LTO) juga berfungsi ketika Anda "mengkompilasi" kode ke dalam file objek individual (tergantung pada apa "kompilasi" artinya bagi Anda). Sebagai contoh, KPP GCC akan menambahkan representasi kode yang diuraikan ke file objek individual pada waktu kompilasi, dan pada waktu tautan akan menggunakan kode tersebut sebagai ganti kode objek (yang juga ada) untuk mengkompilasi ulang dan membangun seluruh program. Jadi ini bekerja dengan membangun setup yang mengkompilasi ke file objek individual terlebih dahulu, meskipun kode mesin yang dihasilkan oleh kompilasi awal diabaikan.
Pemimpi
8
JsonCpp melakukan ini juga saat ini. Kuncinya adalah bahwa file tidak seperti ini selama pengembangan.
Lightness Races dalam Orbit
15

Selain faktor kesederhanaan yang disebutkan oleh responden lain, banyak program C ditulis oleh satu orang.

Saat Anda memiliki tim yang terdiri dari beberapa individu, diinginkan untuk memecah aplikasi menjadi beberapa file sumber untuk menghindari konflik yang serampangan dalam perubahan kode. Terutama ketika ada kedua programmer yang sangat maju dan sangat junior bekerja pada proyek tersebut.

Ketika satu orang bekerja sendiri, itu bukan masalah.

Secara pribadi, saya menggunakan banyak file berdasarkan fungsi sebagai kebiasaan. Tapi itu hanya aku.

Ron Ruble
sumber
4
@OskarSkog Tetapi Anda tidak akan pernah memodifikasi file pada saat yang sama dengan diri Anda di masa depan.
Loren Pechtel
2

Karena C89 tidak memiliki inlinefungsi. Yang berarti bahwa memecah file Anda menjadi fungsi menyebabkan biaya mendorong nilai pada stack dan melompat-lompat. Ini menambahkan sedikit overhead daripada mengimplementasikan kode dalam 1 pernyataan switch besar (event loop). Tetapi suatu loop peristiwa selalu jauh lebih sulit untuk diterapkan secara efisien (atau bahkan dengan benar) daripada solusi yang lebih termodulasi. Jadi untuk proyek-proyek besar, orang masih akan memilih untuk melakukan modularisasi. Tetapi ketika mereka memiliki desain dipikirkan sebelumnya dan dapat mengontrol negara dalam 1 pernyataan switch, mereka memilih untuk itu.

Saat ini, bahkan dalam C, seseorang tidak perlu mengorbankan kinerja untuk memodulasi karena bahkan dalam fungsi C dapat digarisbawahi.

Dmitry Rubanovich
sumber
2
Fungsi C bisa jadi sebanyak sebaris dalam 89 hari ini, sebaris adalah sesuatu yang harus digunakan hampir tidak pernah - kompiler tahu lebih baik daripada Anda dalam hampir semua situasi. Dan sebagian besar file LOC 4k itu bukan satu fungsi raksasa - itu adalah gaya pengkodean yang mengerikan yang tidak akan memiliki manfaat kinerja yang nyata juga.
Voo
@ Oh, saya tidak tahu mengapa Anda menyebutkan gaya pengkodean. Saya tidak menganjurkannya. Bahkan, saya menyebutkan bahwa dalam kebanyakan kasus itu menjamin solusi yang kurang efisien karena implementasi yang gagal. Saya juga menyebutkan bahwa itu adalah ide yang buruk karena tidak skala (untuk proyek yang lebih besar). Karena itu, dalam loop yang sangat ketat (yang merupakan apa yang terjadi dalam kode jaringan yang hampir sama dengan perangkat keras), dengan sia-sia mendorong dan mengeluarkan nilai on / off stack (saat memanggil fungsi) akan menambah biaya program yang sedang berjalan. Ini bukan solusi yang bagus. Tapi itu yang terbaik yang tersedia saat itu.
Dmitry Rubanovich
2
Catatan wajib: kata kunci inline hanya ada sedikit hubungannya dengan pengoptimalan sebaris. Ini bukan petunjuk khusus bagi kompiler untuk melakukan optimasi itu, melainkan berkaitan dengan menghubungkan dengan simbol duplikat.
hyde
@ Sentris Intinya adalah bahwa mengklaim bahwa karena tidak ada inlinekata kunci dalam kompiler C89 tidak bisa sejalan yang mengapa Anda harus menulis semuanya dalam satu fungsi raksasa tidak benar. Sebaiknya Anda tidak pernah menggunakan inlinepengoptimalan kinerja - kompiler pada umumnya akan lebih tahu daripada Anda (dan mengabaikan kata kunci).
Voo
@ Voo: Seorang programmer dan kompiler pada umumnya masing-masing akan mengetahui beberapa hal yang lainnya tidak. Kata inlinekunci tersebut memiliki semantik terkait tautan yang lebih penting daripada pertanyaan apakah melakukan optimasi in-line atau tidak, tetapi beberapa implementasi memiliki arahan lain untuk mengendalikan in-lining dan hal-hal semacam itu kadang-kadang bisa sangat penting. Dalam beberapa kasus, suatu fungsi mungkin terlihat terlalu besar untuk di-lining, tetapi pelipatan konstan dapat mengurangi ukuran dan waktu eksekusi menjadi hampir tidak ada. Kompiler yang tidak diberi dorongan kuat untuk mendorong in-lining mungkin tidak ...
supercat
1

Ini dianggap sebagai contoh evolusi, yang saya terkejut belum disebutkan.

Pada hari-hari gelap pemrograman, kompilasi satu FILE tunggal bisa memakan waktu beberapa menit. Jika suatu program dimodulasi, maka dimasukkannya file header yang diperlukan (tidak ada opsi header yang dikompilasi) akan menjadi penyebab tambahan yang signifikan dari perlambatan. Selain itu kompiler mungkin memilih / perlu menyimpan beberapa informasi pada disk itu sendiri, mungkin tanpa manfaat dari file swap otomatis.

Kebiasaan yang ditimbulkan oleh faktor-faktor lingkungan ini terbawa ke dalam praktik pembangunan yang sedang berlangsung dan hanya perlahan-lahan beradaptasi seiring waktu.

Pada saat itu keuntungan dari menggunakan satu file akan sama dengan yang kita dapatkan dengan menggunakan SSD, bukan HDD.

itj
sumber