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.)
- Konsisten dengan pendekatan Anda, sehingga orang lain yang membaca kode Anda dapat mengembangkan pemahaman tentang bagaimana Anda mendekati proses pengkodean Anda. Menjadi tidak konsisten mungkin merupakan kejahatan terburuk yang mungkin terjadi. Ini tidak hanya mempersulit orang lain, tetapi juga menyulitkan Anda untuk kembali ke kode bertahun-tahun kemudian.
- Sedapat mungkin, coba dan atur hal-hal sehingga inisialisasi berbagai bagian fungsional dapat dilakukan tanpa memperhatikan pemesanan. Dimana diperlukan pemesanan, jika itu karena kopling dekat dari dua subfungsi yang sangat terkait, maka pertimbangkan inisialisasi tunggal untuk keduanya sehingga dapat dipesan ulang tanpa menyebabkan kerusakan. Jika itu tidak memungkinkan, maka dokumentasikan persyaratan pemesanan inisialisasi.
- Meringkas pengetahuan di satu tempat, jika memungkinkan. Konstanta tidak boleh digandakan di semua tempat dalam kode. Persamaan yang memecahkan beberapa variabel harus ada di satu dan hanya satu tempat. Dan seterusnya. Jika Anda menemukan diri Anda menyalin dan menempel beberapa set garis yang melakukan perilaku yang diperlukan di berbagai lokasi, pertimbangkan cara untuk menangkap pengetahuan itu di satu tempat dan menggunakannya di tempat yang diperlukan. Misalnya, jika Anda memiliki struktur pohon yang harus berjalan dengan cara tertentu, lakukan tidakmereplikasi kode pohon berjalan di setiap tempat di mana Anda perlu untuk loop melalui simpul pohon. Alih-alih, tangkap metode berjalan pohon di satu tempat dan gunakan. Dengan cara ini, jika pohon berubah dan metode berjalan berubah, Anda hanya memiliki satu tempat yang perlu dikhawatirkan dan semua kode lainnya "berfungsi dengan baik."
- Jika Anda menyebarkan semua rutinitas Anda ke selembar kertas yang besar dan rata, dengan panah yang menghubungkannya dengan rutinitas lain, Anda akan melihat dalam aplikasi apa pun akan ada "kelompok" rutin yang memiliki banyak dan banyak anak panah antara mereka sendiri tetapi hanya beberapa panah di luar grup. Jadi akan ada batasan alami dari rutinitas yang dipasangkan dengan erat dan koneksi yang dipasangkan secara longgar di antara kelompok-kelompok lain dari rutinitas yang dipasangkan dengan erat. Gunakan fakta ini untuk mengatur kode Anda ke dalam modul. Ini akan membatasi kompleksitas kode Anda yang nyata.
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:
- Keterbatasan biaya yang parah membutuhkan MCU yang sangat primitif dengan RAM sangat kecil dan hampir tanpa pin I / O. Untuk ini, seluruh rangkaian aturan baru berlaku. Misalnya, Anda mungkin harus menulis dalam kode assembly karena tidak ada banyak ruang kode. Anda mungkin harus menggunakan variabel statis HANYA karena penggunaan variabel lokal terlalu mahal dan memakan waktu. Anda mungkin harus menghindari penggunaan subrutin yang berlebihan karena (misalnya, beberapa bagian PIC Microchip) hanya ada 4 register perangkat keras untuk menyimpan alamat pengirim subrutin. Jadi, Anda mungkin harus secara dramatis "meratakan" kode Anda. Dll
- Keterbatasan daya yang parah membutuhkan kode yang dibuat dengan hati-hati untuk memulai dan mematikan sebagian besar MCU dan menempatkan batasan parah pada waktu eksekusi kode ketika berjalan dengan kecepatan penuh. Sekali lagi, ini mungkin memerlukan beberapa kode perakitan, kadang-kadang.
- Persyaratan waktu yang berat. Sebagai contoh, ada saat-saat di mana saya harus memastikan bahwa transmisi saluran terbuka 0 harus menggunakan jumlah siklus yang sama persis dengan transmisi 1. Dan bahwa pengambilan sampel pada jalur yang sama ini juga harus dilakukan dengan fase relatif yang tepat untuk waktu ini. Ini berarti bahwa C TIDAK dapat digunakan di sini. Cara HANYA yang mungkin untuk membuat jaminan itu adalah dengan hati-hati menyusun kode perakitan. (Dan bahkan kemudian, tidak selalu pada semua desain ALU.)
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 :
- spesialisasi templat parsial
- tabel
- objek dasar virtual
- bingkai aktivasi
- bingkai aktivasi bersantai
- penggunaan pointer cerdas dalam konstruktor, dan mengapa
- optimalisasi nilai kembali
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.
SEBUAHB
SEBUAH
.
.
foo ();
String s;
foo ();
.
.
SEBUAH
B
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.)
class A {...};
A rA (A p) { return p; }
// .....
{ A x; rA(x); }
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.