Saya telah melihat beberapa perpustakaan untuk mikrokontroler dan dan fungsinya melakukan satu hal pada satu waktu. Misalnya, sesuatu seperti ini:
void setCLK()
{
// Code to set the clock
}
void setConfig()
{
// Code to set the config
}
void setSomethingElse()
{
// 1 line code to write something to a register.
}
Kemudian datang fungsi lain di atasnya yang menggunakan kode 1 baris ini yang berisi fungsi untuk melayani tujuan lain. Sebagai contoh:
void initModule()
{
setCLK();
setConfig();
setSomethingElse();
}
Saya tidak yakin, tapi saya percaya dengan cara ini akan membuat lebih banyak panggilan untuk melompat dan menciptakan overhead untuk menumpuk alamat kembali setiap kali fungsi dipanggil atau keluar. Dan itu akan membuat program bekerja lambat, bukan?
Saya telah mencari dan di mana-mana mereka mengatakan bahwa aturan praktis pemrograman adalah bahwa suatu fungsi seharusnya hanya melakukan satu tugas.
Jadi jika saya menulis langsung modul fungsi InitModule yang mengatur jam, menambahkan beberapa konfigurasi yang diinginkan dan melakukan sesuatu yang lain tanpa memanggil fungsi. Apakah ini pendekatan yang buruk ketika menulis perangkat lunak tertanam?
EDIT 2:
Sepertinya banyak orang telah memahami pertanyaan ini seolah-olah saya mencoba untuk mengoptimalkan suatu program. Tidak, saya tidak punya niat untuk melakukannya . Saya membiarkan kompiler melakukannya, karena itu akan selalu (saya harap tidak sekalipun!) Lebih baik daripada saya.
Semua menyalahkan saya untuk memilih contoh yang mewakili beberapa kode inisialisasi . Pertanyaannya tidak memiliki niat tentang pemanggilan fungsi yang dilakukan untuk tujuan inisialisasi. Pertanyaan saya adalah Apakah memecah tugas tertentu menjadi fungsi-fungsi kecil multi-line ( jadi in-line keluar dari pertanyaan ) berjalan di dalam infinite loop memiliki keunggulan dibandingkan menulis fungsi panjang tanpa fungsi bersarang?
Silakan mempertimbangkan keterbacaan yang ditentukan dalam jawaban @ Jonk .
sumber
Jawaban:
Bisa dibilang, dalam contoh Anda kinerja tidak akan masalah, karena kode hanya dijalankan sekali saat startup.
Aturan praktis yang saya gunakan: Tulis kode Anda semudah mungkin dibaca dan hanya mulai mengoptimalkan jika Anda melihat bahwa kompiler Anda tidak melakukan sihirnya dengan benar.
Biaya panggilan fungsi dalam ISR mungkin sama dengan biaya panggilan fungsi saat startup dalam hal penyimpanan dan waktu. Namun, persyaratan waktu selama ISR itu mungkin jauh lebih kritis.
Lebih jauh, seperti yang telah diperhatikan oleh orang lain, biaya (dan makna 'biaya') dari panggilan fungsi berbeda menurut platform, kompiler, pengaturan optimisasi kompiler, dan persyaratan aplikasi. Akan ada perbedaan besar antara 8051 dan korteks-m7, dan alat pacu jantung dan lampu.
sumber
Tidak ada keuntungan yang dapat saya pikirkan (tetapi lihat catatan untuk JasonS di bagian bawah), yang membungkus satu baris kode sebagai fungsi atau subrutin. Kecuali mungkin Anda bisa memberi nama fungsi itu sesuatu yang "dapat dibaca." Tapi Anda bisa saja berkomentar. Dan karena membungkus satu baris kode dalam suatu fungsi memerlukan kode memori, ruang stack, dan waktu eksekusi, menurut saya sebagian besar kontraproduktif. Dalam situasi mengajar? Mungkin masuk akal. Tetapi itu tergantung pada kelas siswa, persiapan mereka sebelumnya, kurikulum, dan guru. Sebagian besar, saya pikir itu bukan ide yang baik. Tapi itu menurut saya.
Yang membawa kita pada intinya. Area pertanyaan Anda yang luas telah, selama beberapa dekade, menjadi bahan perdebatan dan hingga hari ini masih menjadi bahan perdebatan. Jadi, setidaknya saat saya membaca pertanyaan Anda, bagi saya itu sepertinya pertanyaan berdasarkan pendapat (seperti yang Anda tanyakan).
Itu bisa dipindahkan dari menjadi berbasis opini seperti itu, jika Anda lebih rinci tentang situasi dan dengan hati-hati menggambarkan tujuan yang Anda pegang sebagai yang utama. Semakin baik Anda mendefinisikan alat ukur Anda, semakin objektif jawabannya.
Secara umum, Anda ingin melakukan hal berikut untuk pengkodean apa pun . (Untuk di bawah ini, saya akan berasumsi bahwa kami membandingkan berbagai pendekatan yang semuanya mencapai tujuan. Jelas, setiap kode yang gagal melakukan tugas yang diperlukan lebih buruk daripada kode yang berhasil, terlepas dari bagaimana itu ditulis.)
Di atas umumnya hanya benar tentang semua coding. Saya tidak membahas penggunaan parameter, variabel global lokal atau statis, dll. Alasannya adalah bahwa untuk pemrograman tertanam ruang aplikasi sering menempatkan kendala baru yang ekstrim dan sangat signifikan dan tidak mungkin untuk membahas semuanya tanpa membahas setiap aplikasi yang disematkan. Dan itu tidak terjadi di sini.
Kendala-kendala ini dapat berupa (dan lebih banyak) di antaranya:
Dan seterusnya. (Kode pengkabelan untuk instrumentasi medis yang kritis terhadap kehidupan memiliki seluruh dunianya sendiri.)
Hasilnya di sini adalah bahwa pengkodean yang disematkan seringkali bukan program gratis untuk semua, di mana Anda dapat membuat kode seperti yang Anda lakukan di workstation. Seringkali ada alasan yang kuat dan kompetitif untuk berbagai kendala yang sangat sulit. Dan ini mungkin sangat menentang jawaban yang lebih tradisional dan stok .
Mengenai keterbacaan, saya menemukan bahwa kode dapat dibaca jika ditulis dengan cara yang konsisten yang dapat saya pelajari saat saya membacanya. Dan di mana tidak ada upaya yang disengaja untuk mengaburkan kode. Sebenarnya tidak banyak yang diperlukan.
Kode yang dapat dibaca bisa sangat efisien dan dapat memenuhi semua persyaratan di atas yang telah saya sebutkan. Hal utama adalah bahwa Anda sepenuhnya memahami apa yang dihasilkan setiap baris kode yang Anda tulis pada tingkat perakitan atau mesin, saat Anda membuat kode. C ++ menempatkan beban yang serius pada programmer di sini karena ada banyak situasi di mana identik potongan kode C ++ benar-benar menghasilkan berbagai potongan kode mesin yang memiliki kinerja sangat berbeda. Tapi C, umumnya, sebagian besar adalah bahasa "apa yang Anda lihat adalah apa yang Anda dapatkan". Jadi lebih aman dalam hal itu.
EDIT per JasonS:
Saya telah menggunakan C sejak 1978 dan C ++ sejak sekitar 1987 dan saya memiliki banyak pengalaman menggunakan keduanya untuk mainframe, minicomputer, dan (kebanyakan) aplikasi yang disematkan.
Jason memunculkan komentar tentang menggunakan 'inline' sebagai pengubah. (Dalam perspektif saya, ini adalah kemampuan yang relatif "baru" karena itu tidak ada selama mungkin setengah dari hidup saya atau lebih menggunakan C dan C ++.) Penggunaan fungsi inline sebenarnya dapat membuat panggilan seperti itu (bahkan untuk satu baris kode) cukup praktis. Dan itu jauh lebih baik, jika memungkinkan, daripada menggunakan makro karena pengetikan yang dapat diterapkan oleh kompiler.
Tetapi ada keterbatasan juga. Yang pertama adalah Anda tidak bisa mengandalkan kompiler untuk "menerima petunjuk." Mungkin, atau mungkin tidak. Dan ada alasan bagus untuk tidak menerima petunjuk itu. (Untuk contoh yang jelas, jika alamat fungsi diambil, ini membutuhkan instantiasi fungsi dan penggunaan alamat untuk membuat panggilan akan ... memerlukan panggilan. Kode tidak dapat digariskan kemudian.) Ada alasan lain juga. Kompiler dapat memiliki beragam kriteria yang digunakan untuk menilai cara menangani petunjuk. Dan sebagai seorang programmer, ini berarti Anda harusLuangkan waktu untuk mempelajari aspek kompiler itu atau Anda mungkin akan membuat keputusan berdasarkan ide-ide yang salah. Jadi itu menambah beban bagi penulis kode dan juga setiap pembaca dan siapa pun yang berencana untuk port kode ke beberapa kompiler lain, juga.
Juga, kompiler C dan C ++ mendukung kompilasi terpisah. Ini berarti bahwa mereka dapat mengkompilasi satu bagian dari kode C atau C ++ tanpa mengkompilasi kode terkait lainnya untuk proyek tersebut. Untuk kode inline, anggap kompiler jika tidak memilih untuk melakukannya, itu tidak hanya harus memiliki deklarasi "dalam ruang lingkup" tetapi juga harus memiliki definisi, juga. Biasanya, programmer akan bekerja untuk memastikan bahwa ini adalah kasusnya jika mereka menggunakan 'inline'. Tetapi mudah untuk kesalahan untuk masuk.
Secara umum, sementara saya juga menggunakan inline di mana saya pikir itu sesuai, saya cenderung berasumsi bahwa saya tidak dapat mengandalkannya. Jika kinerja adalah persyaratan yang signifikan, dan saya pikir OP telah dengan jelas menulis bahwa ada hit kinerja yang signifikan ketika mereka pergi ke rute yang lebih "fungsional", maka saya pasti akan memilih untuk menghindari mengandalkan inline sebagai praktik pengkodean dan sebaliknya akan mengikuti pola penulisan kode yang sedikit berbeda, tetapi sepenuhnya konsisten.
Catatan akhir tentang 'sebaris' dan definisi menjadi "dalam ruang lingkup" untuk langkah kompilasi yang terpisah. Adalah mungkin (tidak selalu dapat diandalkan) untuk pekerjaan yang harus dilakukan pada tahap menghubungkan. Ini dapat terjadi jika dan hanya jika kompiler C / C ++ mengubur detail yang cukup ke dalam file objek untuk memungkinkan linker untuk bertindak atas permintaan 'inline'. Saya pribadi belum mengalami sistem tautan (di luar Microsoft) yang mendukung kemampuan ini. Tetapi itu bisa terjadi. Sekali lagi, apakah itu harus diandalkan atau tidak akan tergantung pada keadaan. Tapi saya biasanya menganggap ini belum disekop ke linker, kecuali saya tahu sebaliknya berdasarkan bukti yang baik. Dan jika saya mengandalkannya, itu akan didokumentasikan di tempat yang menonjol.
C ++
Bagi mereka yang tertarik, berikut adalah contoh mengapa saya tetap cukup berhati-hati terhadap C ++ saat mengkode aplikasi yang disematkan, meskipun sudah tersedia saat ini. Saya akan membuang beberapa istilah yang saya pikir semua programmer C ++ tertanam perlu tahu dingin :
Itu hanya daftar pendek. Jika Anda belum mengetahui segala hal tentang persyaratan tersebut dan mengapa saya mencantumkannya (dan banyak lagi yang tidak saya sebutkan di sini) maka saya akan menyarankan agar tidak menggunakan C ++ untuk pekerjaan yang disematkan, kecuali jika itu bukan opsi untuk proyek .
Mari kita melihat semantik pengecualian C ++ untuk mendapatkan rasa.
Kompilator C ++ melihat panggilan pertama ke foo () dan bisa membiarkan frame aktivasi yang normal terjadi, jika foo () melempar pengecualian. Dengan kata lain, kompiler C ++ tahu bahwa tidak ada kode tambahan yang diperlukan pada saat ini untuk mendukung proses pelepasan frame yang terlibat dalam penanganan pengecualian.
Tapi begitu String s telah dibuat, kompiler C ++ tahu bahwa itu harus dihancurkan dengan benar sebelum frame reasing dapat diizinkan, jika pengecualian terjadi kemudian. Jadi panggilan kedua ke foo () secara semantik berbeda dari yang pertama. Jika panggilan ke-2 untuk foo () melempar pengecualian (yang mungkin atau mungkin tidak dilakukan), kompiler harus telah menempatkan kode yang dirancang untuk menangani penghancuran String sebelum membiarkan frame biasa melepas terjadi. Ini berbeda dari kode yang diperlukan untuk panggilan pertama ke foo ().
(Dimungkinkan untuk menambahkan dekorasi tambahan dalam C ++ untuk membantu membatasi masalah ini. Tetapi kenyataannya, programmer yang menggunakan C ++ harus jauh lebih sadar akan implikasi dari setiap baris kode yang mereka tulis.)
Tidak seperti C malloc, C ++ baru menggunakan pengecualian untuk memberi sinyal ketika tidak dapat melakukan alokasi memori mentah. Begitu juga dengan 'dynamic_cast'. (Lihat edisi ke-3 Stroustrup., The C ++ Programming Language, halaman 384 dan 385 untuk pengecualian standar dalam C ++.) Compiler memungkinkan perilaku ini dinonaktifkan. Tetapi secara umum Anda akan dikenakan beberapa overhead karena pengecualian yang dibentuk dengan benar menangani prolog dan epilog dalam kode yang dihasilkan, bahkan ketika pengecualian sebenarnya tidak terjadi dan bahkan ketika fungsi yang dikompilasi tidak benar-benar memiliki pengecualian penanganan blok. (Stroustrup telah secara terbuka menyesalkan hal ini.)
Tanpa spesialisasi templat parsial (tidak semua kompiler C ++ mendukungnya), penggunaan templat dapat mengeja bencana untuk pemrograman tertanam. Tanpa itu, kode mekar adalah risiko serius yang dapat membunuh proyek memori kecil tertanam dalam sekejap.
Ketika fungsi C ++ mengembalikan objek, kompiler sementara yang tidak disebutkan namanya dibuat dan dihancurkan. Beberapa kompiler C ++ dapat memberikan kode efisien jika konstruktor objek digunakan dalam pernyataan kembali, alih-alih objek lokal, mengurangi kebutuhan konstruksi dan penghancuran oleh satu objek. Tetapi tidak setiap kompiler melakukan ini dan banyak programmer C ++ bahkan tidak menyadari "optimasi nilai pengembalian" ini.
Memberikan konstruktor objek dengan tipe parameter tunggal dapat memungkinkan kompiler C ++ untuk menemukan jalur konversi antara dua tipe dengan cara yang sama sekali tidak terduga untuk programmer. Perilaku "pintar" semacam ini bukan bagian dari C.
Klausa tangkapan yang menetapkan jenis dasar akan "mengiris" objek turunan yang dilempar, karena objek yang dilempar disalin menggunakan "tipe statis" klausa dan bukan "tipe dinamis" objek. Sumber kesengsaraan pengecualian yang tidak biasa (ketika Anda merasa Anda bahkan mampu membayar pengecualian dalam kode yang disematkan.)
Kompiler C ++ dapat secara otomatis menghasilkan konstruktor, destruktor, salin konstruktor, dan operator penugasan untuk Anda, dengan hasil yang tidak diinginkan. Butuh waktu untuk mendapatkan fasilitas dengan perincian ini.
Melewati array objek yang diturunkan ke fungsi yang menerima array objek dasar, jarang menghasilkan peringatan kompiler tetapi hampir selalu menghasilkan perilaku yang salah.
Karena C ++ tidak memanggil destruktor objek yang dibangun sebagian ketika pengecualian terjadi di konstruktor objek, penanganan pengecualian pada konstruktor biasanya mengamanatkan "smart pointer" untuk menjamin bahwa fragmen yang dibangun dalam konstruktor dihancurkan dengan benar jika pengecualian terjadi di sana . (Lihat Stroustrup, halaman 367 dan 368.) Ini adalah masalah umum dalam menulis kelas yang bagus dalam C ++, tetapi tentu saja dihindari dalam C karena C tidak memiliki semantik konstruksi dan penghancuran yang dibangun. Menulis kode yang tepat untuk menangani konstruksi sub-objek dalam suatu objek berarti menulis kode yang harus mengatasi masalah semantik unik ini di C ++; dengan kata lain "menulis sekitar" perilaku semantik C ++.
C ++ dapat menyalin objek yang diteruskan ke parameter objek. Misalnya, dalam fragmen berikut, panggilan "rA (x);" dapat menyebabkan kompiler C ++ memanggil konstruktor untuk parameter p, untuk kemudian memanggil copy constructor untuk mentransfer objek x ke parameter p, lalu konstruktor lain untuk objek kembali (sementara yang tidak disebutkan namanya) dari fungsi rA, yang tentu saja disalin dari parameter p. Lebih buruk lagi, jika kelas A memiliki objek sendiri yang membutuhkan konstruksi, ini dapat teleskop dengan bencana. (Programmer AC akan menghindari sebagian besar dari sampah ini, mengoptimalkan tangan karena programmer C tidak memiliki sintaks yang sangat berguna dan harus mengekspresikan semua detail satu per satu.)
Akhirnya, catatan singkat untuk programmer C. longjmp () tidak memiliki perilaku portabel di C ++. (Beberapa programmer C menggunakan ini sebagai semacam mekanisme "pengecualian".) Beberapa kompiler C ++ sebenarnya akan mencoba untuk mengatur hal-hal untuk membersihkan ketika longjmp diambil, tetapi perilaku itu tidak portabel di C ++. Jika kompiler tidak membersihkan objek yang dibangun, itu tidak portabel. Jika kompilator tidak membersihkannya, maka objek tidak rusak jika kode meninggalkan ruang lingkup objek yang dibangun sebagai akibat dari longjmp dan perilaku tidak valid. (Jika penggunaan longjmp di foo () tidak meninggalkan ruang lingkup, maka perilakunya mungkin baik-baik saja.) Ini tidak terlalu sering digunakan oleh programmer yang tertanam C tetapi mereka harus membuat diri mereka menyadari masalah ini sebelum menggunakannya.
sumber
inline static void turnOnFan(void) { PORTAbits &= ~(1<<8); }
yang disebut di banyak tempat adalah kandidat yang sempurna.1) Kode untuk keterbacaan dan pemeliharaan pertama. Aspek yang paling penting dari basis kode apa pun adalah bahwa ia terstruktur dengan baik. Perangkat lunak yang ditulis dengan baik cenderung memiliki lebih sedikit kesalahan. Anda mungkin perlu membuat perubahan dalam beberapa minggu / bulan / tahun, dan ini sangat membantu jika kode Anda enak dibaca. Atau mungkin orang lain harus melakukan perubahan.
2) Kinerja kode yang berjalan sekali tidak terlalu menjadi masalah. Peduli gaya, bukan kinerja
3) Bahkan kode dalam loop ketat harus benar terlebih dahulu dan terutama. Jika Anda menghadapi masalah kinerja, maka optimalkan setelah kode benar.
4) Jika Anda perlu mengoptimalkan, Anda harus mengukur! Tidak masalah jika Anda berpikir atau seseorang memberitahu Anda bahwa
static inline
itu hanya rekomendasi untuk kompiler. Anda harus melihat apa yang dilakukan kompiler. Anda juga harus mengukur apakah inlining meningkatkan kinerja. Dalam sistem embedded, Anda juga harus mengukur ukuran kode, karena memori kode biasanya sangat terbatas. Ini adalah aturan terpenting yang membedakan teknik dari tebakan. Jika Anda tidak mengukurnya, itu tidak membantu. Rekayasa adalah pengukuran. Sains menuliskannya;)sumber
Ketika suatu fungsi dipanggil hanya di satu tempat (bahkan di dalam fungsi lain) kompiler selalu meletakkan kode di tempat itu alih-alih benar-benar memanggil fungsi. Jika fungsi ini dipanggil di banyak tempat daripada yang masuk akal untuk menggunakan fungsi setidaknya dari sudut pandang ukuran kode.
Setelah mengkompilasi kode tidak akan memiliki beberapa panggilan saja, keterbacaan akan sangat ditingkatkan.
Anda juga akan ingin memiliki misalnya kode init ADC di perpustakaan yang sama dengan fungsi ADC lain yang tidak ada dalam file c utama.
Banyak kompiler memungkinkan Anda untuk menentukan berbagai tingkat optimasi untuk kecepatan atau ukuran kode, jadi jika Anda memiliki fungsi kecil yang dipanggil di banyak tempat maka fungsi tersebut akan "digariskan", disalin di sana alih-alih memanggil.
Optimalisasi untuk kecepatan akan menyatukan fungsi di sebanyak mungkin tempat, optimisasi untuk ukuran kode akan memanggil fungsi, namun, ketika suatu fungsi dipanggil hanya di satu tempat seperti pada kasus Anda, ia akan selalu "digariskan".
Kode seperti ini:
akan dikompilasi ke:
tanpa menggunakan panggilan apa pun.
Dan jawaban untuk pertanyaan Anda, dalam contoh Anda atau yang serupa, keterbacaan kode tidak mempengaruhi kinerja, tidak ada yang banyak dalam kecepatan atau ukuran kode. Adalah umum untuk menggunakan beberapa panggilan hanya untuk membuat kode dapat dibaca, pada akhirnya mereka dipatuhi sebagai kode inline.
Pembaruan untuk menentukan bahwa pernyataan di atas tidak berlaku untuk kompiler versi gratis yang sengaja dilumpuhkan seperti versi gratis Microchip XCxx. Panggilan fungsi semacam ini adalah tambang emas untuk Microchip untuk menunjukkan seberapa jauh lebih baik versi berbayar dan jika Anda mengompilasinya, Anda akan menemukan di ASM persis sama banyaknya panggilan yang Anda miliki dalam kode C.
Juga bukan untuk programmer bodoh yang berharap untuk menggunakan pointer ke fungsi inline.
Ini adalah bagian elektronik, bukan C ++ umum atau bagian pemrograman, pertanyaannya adalah tentang pemrograman mikrokontroler di mana setiap kompiler yang layak akan melakukan optimasi di atas secara default.
Jadi tolong hentikan pengambilan suara hanya karena dalam kasus yang jarang dan tidak biasa ini mungkin tidak benar.
sumber
Pertama, tidak ada yang terbaik atau terburuk; itu semua masalah pendapat. Anda benar bahwa ini tidak efisien. Itu bisa dioptimalkan atau tidak; tergantung. Biasanya Anda akan melihat jenis fungsi ini, jam, GPIO, timer, dll. Di file / direktori terpisah. Kompiler umumnya belum dapat mengoptimalkan celah ini. Ada satu yang bisa saya ketahui tetapi tidak banyak digunakan untuk hal-hal seperti ini.
File tunggal:
Memilih target dan kompiler untuk tujuan demonstrasi.
Inilah yang sebagian besar jawaban di sini memberi tahu Anda, bahwa Anda naif dan semua ini dioptimalkan dan fungsinya dihapus. Ya, mereka tidak dihapus karena secara global didefinisikan secara default. Kami dapat menghapusnya jika tidak diperlukan di luar file yang satu ini.
menghapusnya sekarang seperti yang diuraikan.
Tetapi kenyataannya adalah ketika Anda mengambil vendor chip atau perpustakaan BSP,
Anda pasti akan mulai menambahkan overhead, yang memiliki biaya nyata untuk kinerja dan ruang. Beberapa hingga lima persen dari masing-masing tergantung pada seberapa kecil fungsi masing-masing.
Mengapa ini dilakukan? Beberapa di antaranya adalah seperangkat aturan yang akan atau masih diajarkan profesor untuk membuat kode penilaian lebih mudah. Fungsinya harus sesuai pada halaman (saat Anda mencetak karya Anda di atas kertas), jangan lakukan ini, jangan lakukan itu, dll. Banyak yang membuat perpustakaan dengan nama umum untuk target yang berbeda. Jika Anda memiliki puluhan keluarga mikrokontroler, beberapa di antaranya berbagi periferal dan beberapa tidak, mungkin tiga atau empat rasa UART berbeda dicampur di antara keluarga, GPIO yang berbeda, pengontrol SPI, dll. Anda dapat memiliki fungsi gpio_init () yang umum, get_timer_count (), dll. Dan gunakan kembali abstraksi tersebut untuk periferal yang berbeda.
Ini menjadi kasus sebagian besar rawatan dan desain perangkat lunak, dengan beberapa kemungkinan keterbacaan. Maintabilitas, keterbacaan, dan kinerja Anda tidak bisa memiliki semuanya; Anda hanya dapat memilih satu atau dua sekaligus, bukan ketiganya.
Ini sangat banyak pertanyaan berbasis opini, dan di atas menunjukkan tiga cara utama ini bisa pergi. Seperti apa jalan TERBAIK yang sepenuhnya merupakan pendapat. Apakah melakukan semua pekerjaan dalam satu fungsi? Pertanyaan berbasis opini, beberapa orang cenderung untuk kinerja, beberapa mendefinisikan modularitas dan versi keterbacaan mereka sebagai TERBAIK. Masalah menarik dengan apa yang oleh banyak orang disebut keterbacaan sangat menyakitkan; untuk "melihat" kode Anda harus membuka 50-10.000 file sekaligus dan entah bagaimana mencoba melihat fungsi-fungsi dalam urutan eksekusi untuk melihat apa yang sedang terjadi. Saya menemukan bahwa kebalikan dari keterbacaan, tetapi yang lain menemukannya dapat dibaca karena setiap item cocok di layar / editor jendela dan dapat dikonsumsi secara keseluruhan setelah mereka menghafal fungsi yang dipanggil dan / atau memiliki editor yang dapat keluar masuk dari setiap fungsi dalam suatu proyek.
Itu adalah faktor besar lain ketika Anda melihat berbagai solusi. Editor teks, IDE, dll. Sangat pribadi, dan melampaui vi vs Emacs. Pemrograman efisiensi, garis per hari / bulan naik jika Anda merasa nyaman dan efisien dengan alat yang Anda gunakan. Fitur-fitur alat ini dapat / akan dengan sengaja atau tidak condong ke arah bagaimana penggemar alat itu menulis kode. Dan sebagai hasilnya jika seseorang menulis perpustakaan-perpustakaan ini, proyek tersebut sampai batas tertentu mencerminkan kebiasaan-kebiasaan ini. Bahkan jika itu adalah sebuah tim, kebiasaan / preferensi pemimpin pengembang atau bos mungkin dipaksa ke anggota tim lainnya.
Standar pengkodean yang memiliki banyak preferensi pribadi terkubur di dalamnya, sangat religius vs Emacs lagi, tab vs spasi, bagaimana tanda kurung berbaris, dll. Dan ini menunjukkan bagaimana perpustakaan dirancang sampai batas tertentu.
Bagaimana seharusnya Anda menulis milik Anda? Bagaimanapun yang Anda inginkan, sebenarnya tidak ada jawaban yang salah jika berfungsi. Tentu ada kode yang buruk atau berisiko, tetapi jika ditulis sedemikian rupa sehingga Anda dapat mempertahankannya sesuai kebutuhan, itu memenuhi tujuan desain Anda, menyerah pada keterbacaan dan beberapa pemeliharaan jika kinerja penting, atau sebaliknya. Apakah Anda suka nama variabel pendek sehingga satu baris kode sesuai dengan lebar jendela editor? Atau nama yang terlalu deskriptif panjang untuk menghindari kebingungan, tetapi keterbacaan menurun karena Anda tidak bisa mendapatkan satu baris pada satu halaman; sekarang secara visual putus, mengacaukan aliran.
Anda tidak akan mencapai home run pertama kali di kelelawar. Mungkin perlu / harus puluhan tahun untuk benar-benar mendefinisikan gaya Anda. Pada saat yang sama, dari waktu ke waktu, gaya Anda mungkin berubah, condong satu arah untuk sementara waktu, lalu condong ke arah lain.
Anda akan mendengar banyak dari tidak mengoptimalkan, tidak pernah mengoptimalkan, dan optimasi prematur. Tetapi seperti yang ditunjukkan, desain seperti ini dari awal menciptakan masalah kinerja, kemudian Anda mulai melihat peretasan untuk menyelesaikan masalah itu alih-alih mendesain ulang dari awal untuk melakukan. Saya setuju ada beberapa situasi, satu fungsi beberapa baris kode yang Anda dapat berusaha keras untuk memanipulasi kompiler berdasarkan pada ketakutan apa yang akan dilakukan kompiler sebaliknya (perhatikan dengan pengalaman pengkodean semacam ini menjadi mudah dan alami, mengoptimalkan ketika Anda menulis mengetahui bagaimana kompiler akan mengkompilasi kode), maka Anda ingin mengkonfirmasi di mana pencuri siklus sebenarnya, sebelum menyerang itu.
Anda juga perlu merancang kode Anda untuk pengguna sampai batas tertentu. Jika ini adalah proyek Anda, Anda adalah satu-satunya pengembang; apapun yang kamu inginkan. Jika Anda mencoba membuat perpustakaan untuk diberikan atau dijual, Anda mungkin ingin membuat kode Anda terlihat seperti semua perpustakaan lain, ratusan hingga ribuan file dengan fungsi kecil, nama fungsi panjang, dan nama variabel panjang. Terlepas dari masalah keterbacaan dan masalah kinerja, IMO Anda akan menemukan lebih banyak orang akan dapat menggunakan kode itu.
sumber
Aturan yang sangat umum - kompiler dapat mengoptimalkan lebih baik daripada Anda. Tentu saja, ada pengecualian jika Anda melakukan hal-hal yang sangat intensif, tetapi secara keseluruhan jika Anda menginginkan optimasi yang baik untuk kecepatan atau ukuran kode, pilih kompiler Anda dengan bijak.
sumber
Itu pasti tergantung pada gaya pengkodean Anda sendiri. Satu aturan umum yang ada di luar sana adalah, bahwa nama variabel serta nama fungsi harus sejelas dan sejelas mungkin menjelaskan sendiri. Semakin banyak sub-panggilan atau baris kode yang Anda masukkan ke dalam suatu fungsi, semakin sulit untuk mendefinisikan tugas yang jelas untuk fungsi yang satu itu. Dalam contoh Anda, Anda memiliki fungsi
initModule()
yang menginisialisasi hal-hal dan memanggil sub-rutin yang kemudian mengatur jam atau mengatur konfigurasi . Anda dapat mengetahui itu hanya dengan membaca nama fungsi. Jika Anda meletakkan semua kode dari subrutin di komputer AndainitModule()
secara langsung, itu menjadi kurang jelas apa fungsi sebenarnya. Tapi seperti sering, itu hanya panduan.sumber
Jika suatu fungsi benar-benar hanya melakukan satu hal yang sangat kecil, pertimbangkan membuatnya
static inline
.Tambahkan ke file header bukan file C, dan gunakan kata-kata
static inline
untuk mendefinisikannya:Sekarang, jika fungsinya bahkan sedikit lebih lama, seperti menjadi lebih dari 3 baris, mungkin ide yang baik untuk menghindari
static inline
dan menambahkannya ke file .c. Bagaimanapun, sistem tertanam memiliki memori terbatas, dan Anda tidak ingin menambah ukuran kode terlalu banyak.Juga, jika Anda mendefinisikan fungsi dalam
file1.c
dan menggunakannya darifile2.c
, kompiler tidak akan secara otomatis menyejajarkannya. Namun, jika Anda mendefinisikannyafile1.h
sebagai suatustatic inline
fungsi, kemungkinan kompiler Anda mengikutinya.static inline
Fungsi - fungsi ini sangat berguna dalam pemrograman kinerja tinggi. Saya telah menemukan mereka untuk meningkatkan kinerja kode sering dengan faktor lebih dari tiga.sumber
file1.c
dan menggunakannya darifile2.c
, kompiler tidak akan secara otomatis menyejajarkannya. Salah . Lihat misalnya-flto
di gcc atau dentang.Satu kesulitan dengan mencoba menulis kode yang efisien dan andal untuk mikrokontroler adalah bahwa beberapa kompiler tidak dapat menangani semantik tertentu dengan andal kecuali kode menggunakan arahan khusus kompiler atau menonaktifkan banyak optimisasi.
Misalnya, jika memiliki sistem inti tunggal dengan rutinitas layanan interupsi [dijalankan oleh kutu pengatur waktu atau apa pun]:
seharusnya dimungkinkan untuk menulis fungsi untuk memulai operasi penulisan latar belakang atau menunggu sampai selesai:
dan kemudian panggil kode tersebut menggunakan:
Sayangnya, dengan optimalisasi penuh diaktifkan, kompiler "pintar" seperti gcc atau dentang akan memutuskan bahwa tidak ada cara set pertama menulis dapat memiliki efek pada diamati dari program dan dengan demikian mereka dapat dioptimalkan. Penyusun kualitas seperti
icc
cenderung tidak melakukan hal ini jika tindakan menetapkan penyelesaian interupsi dan menunggu melibatkan penulisan yang tidak stabil dan pembacaan tidak stabil (seperti halnya di sini), tetapi platform yang ditargetkan olehicc
tidak begitu populer untuk sistem embedded.Standar sengaja mengabaikan masalah kualitas implementasi, memperkirakan bahwa ada beberapa cara masuk akal konstruk di atas dapat ditangani:
Implementasi kualitas yang ditujukan khusus untuk bidang-bidang seperti pengelompokan angka kelas atas dapat beralasan mengharapkan bahwa kode yang ditulis untuk bidang semacam itu tidak akan mengandung konstruksi seperti di atas.
Implementasi kualitas dapat memperlakukan semua akses ke
volatile
objek seolah-olah mereka dapat memicu tindakan yang akan mengakses objek apa pun yang terlihat oleh dunia luar.Implementasi sederhana namun berkualitas baik yang ditujukan untuk penggunaan sistem tertanam mungkin memperlakukan semua panggilan ke fungsi yang tidak ditandai "inline" seolah-olah mereka dapat mengakses objek apa pun yang telah terpapar ke dunia luar, bahkan jika itu tidak memperlakukan
volatile
seperti yang dijelaskan dalam # 2.Standar ini tidak berupaya untuk menyarankan pendekatan mana di atas yang paling sesuai untuk implementasi kualitas, atau untuk mengharuskan implementasi yang "sesuai" memiliki kualitas yang cukup baik untuk dapat digunakan untuk tujuan tertentu. Akibatnya, beberapa kompiler seperti gcc atau dentang secara efektif mensyaratkan bahwa setiap kode yang ingin menggunakan pola ini harus dikompilasi dengan banyak optimasi yang dinonaktifkan.
Dalam beberapa kasus, memastikan bahwa fungsi I / O berada dalam unit kompilasi yang terpisah dan kompiler tidak akan memiliki pilihan selain berasumsi mereka dapat mengakses setiap subset objek yang sewenang-wenang yang telah terpapar ke dunia luar mungkin paling tidak masuk akal- of-Evils cara menulis kode yang akan bekerja andal dengan gcc dan dentang. Namun, dalam kasus seperti itu, tujuannya bukan untuk menghindari biaya tambahan dari panggilan fungsi yang tidak perlu, tetapi untuk menerima biaya yang seharusnya tidak perlu dengan imbalan mendapatkan semantik yang diperlukan.
sumber
volatile
akses seolah-olah mereka berpotensi memicu akses sewenang-wenang ke objek lain), tetapi untuk alasan apa pun gcc dan dentang lebih suka memperlakukan masalah kualitas implementasi sebagai undangan untuk berperilaku dengan cara yang tidak berguna.buff
tidak dinyatakanvolatile
, itu tidak akan diperlakukan sebagai variabel volatil, akses ke sana dapat disusun ulang atau dioptimalkan sepenuhnya jika tampaknya tidak digunakan nanti. Aturannya sederhana: tandai semua variabel yang mungkin diakses di luar aliran program normal (seperti yang dilihat oleh kompiler) sebagaivolatile
. Apakah kontenbuff
diakses dalam interrupt handler? Iya. Maka seharusnyavolatile
.magic_write_count
nol, penyimpanan dimiliki oleh jalur utama. Ketika bukan nol, itu dimiliki oleh pengendali interupsi. Membuatbuff
volatile akan mengharuskan setiap fungsi di mana saja yang beroperasi menggunakanvolatile
pointer-memenuhi syarat, yang akan mengganggu optimasi jauh lebih banyak daripada memiliki kompiler ...