Apakah lebih baik menggunakan perintah preprocessor directive atau if (konstan)?

10

Katakanlah kita memiliki basis kode yang digunakan untuk banyak pelanggan yang berbeda, dan kami memiliki beberapa kode di dalamnya yang relevan hanya untuk pelanggan tipe X. Apakah lebih baik menggunakan arahan preprosesor untuk memasukkan kode ini hanya pada pelanggan tipe X, atau untuk menggunakan pernyataan if? Agar lebih jelas:

// some code
#if TYPE_X_COSTUMER  = 1
// do some things
#endif
// rest of the code

atau

if(TYPE_X_COSTUMER) {
    // do some things
}

Argumen yang bisa saya pikirkan adalah:

  • Arahan preprosesor menghasilkan jejak kode yang lebih kecil dan lebih sedikit cabang (pada kompiler yang tidak mengoptimalkan)
  • Jika pernyataan dihasilkan dengan kode yang selalu dikompilasi, misalnya jika seseorang akan membuat kesalahan yang akan merusak kode yang tidak relevan untuk proyek yang dikerjakannya, kesalahan akan tetap muncul, dan ia tidak akan merusak basis kode. Kalau tidak, dia tidak akan menyadari korupsi.
  • Saya selalu diberitahu untuk memilih penggunaan prosesor daripada penggunaan preprocessor (Jika ini argumen sama sekali ...)

Apa yang lebih disukai - ketika berbicara tentang basis kode untuk banyak pelanggan yang berbeda?

MByD
sumber
3
Berapa kemungkinan Anda akan mengirimkan bangunan yang tidak dibuat menggunakan kompiler pengoptimal? Dan jika itu terjadi, apakah dampaknya akan berpengaruh (terutama jika diletakkan dalam konteks dengan semua optimasi lain yang Anda akan lewatkan)?
@delnan - Kode ini digunakan untuk banyak platform berbeda dengan banyak kompiler berbeda. Dan mengenai dampaknya - mungkin dalam kasus-kasus tertentu.
MByD
Anda disuruh memilih sesuatu? Itu seperti memaksa seseorang untuk makan makanan dari KFC sementara dia tidak suka ayam.
rightfold
@ IPT - tidak, saya tidak tertarik untuk tahu lebih banyak, jadi saya akan membuat keputusan yang lebih baik di masa depan.
MByD
Dalam contoh kedua Anda, apakah TYPE_X_CUSTOMERmasih makro preprosesor?
detly

Jawaban:

5

Saya pikir ada satu keuntungan menggunakan #define yang tidak Anda sebutkan, dan itu adalah fakta bahwa Anda dapat mengatur nilai pada baris perintah (dengan demikian, atur dari skrip build satu langkah Anda).

Selain itu, umumnya lebih baik menghindari makro. Mereka tidak menghargai pelingkupan dan yang dapat menyebabkan masalah. Hanya kompiler yang sangat bodoh yang tidak dapat mengoptimalkan kondisi berdasarkan konstanta waktu kompilasi. Saya tidak tahu apakah itu merupakan masalah bagi produk Anda (misalnya mungkin penting untuk menjaga kode kecil pada platform tertanam).

Tamás Szelei
sumber
Sebenarnya, ini adalah untuk embedded system
MByD
Masalahnya adalah bahwa sistem embedded biasanya menggunakan beberapa kompiler kuno (seperti misalnya gcc 2.95).
BЈовић
@ VJo - GCC adalah kasus keberuntungan :)
MByD
Cobalah. Jika itu termasuk kode yang tidak digunakan dan itu ukuran yang cukup besar, maka pergi untuk menentukan.
Tamás Szelei
Jika Anda ingin dapat mengaturnya melalui baris perintah kompiler, saya masih menggunakan konstanta dalam kode itu sendiri dan hanya # mendefinisikan nilai yang ditetapkan untuk konstanta itu.
CodesInChaos
12

Adapun banyak pertanyaan, jawaban dari pertanyaan ini tergantung . Alih-alih mengatakan mana yang lebih baik saya lebih suka memberikan contoh dan tujuan di mana yang satu lebih baik dari yang lain.

Baik preprosesor maupun konstanta memiliki tempat sendiri untuk penggunaan yang sesuai.

Dalam hal pra-prosesor, kode dihapus sebelum waktu kompilasi. Oleh karena itu, paling cocok untuk situasi di mana kode diharapkan tidak dikompilasi . Ini dapat memengaruhi struktur modul, dependensi, dan ini memungkinkan pemilihan segmen kode terbaik untuk aspek kinerja. Dalam kasus-kasus berikut, seseorang harus membagi kode hanya dengan menggunakan preprosesor.

  1. Kode multi-platform:
    Misalnya, ketika kode dikompilasi di bawah platform yang berbeda, ketika kode memiliki ketergantungan pada nomor versi OS tertentu (atau bahkan versi kompiler - meskipun ini sangat jarang). Sebagai contoh ketika Anda berurusan dengan rekan-rekan kecil-endien kode besar-endien - mereka harus dipisahkan dengan preprocessor daripada konstanta. Atau jika Anda mengkompilasi kode untuk Windows juga Linux dan panggilan sistem tertentu sangat berbeda.

  2. Patch Eksperimental:
    Kasus lain yang membuat ini dibenarkan adalah beberapa kode eksperimental yang berisiko atau modul utama tertentu yang perlu dihilangkan yang akan memiliki hubungan signifikan atau perbedaan kinerja. Alasan mengapa seseorang ingin menonaktifkan kode melalui preprocessor daripada bersembunyi di bawah if () adalah karena kami mungkin tidak yakin bug yang diperkenalkan oleh set perubahan khusus ini dan kami berjalan di bawah dasar eksperimental. Jika gagal, kita harus melakukan apa pun selain menonaktifkan kode itu dalam produksi selain menulis ulang. Beberapa waktu yang ideal untuk digunakan #if 0 untuk mengomentari seluruh kode.

  3. Berurusan dengan dependensi:
    Alasan lain di mana Anda mungkin ingin membuat Sebagai contoh, jika Anda tidak ingin mendukung gambar JPEG, Anda dapat membantu menyingkirkan mengkompilasi modul / rintisan itu dan akhirnya perpustakaan tidak akan (secara statis atau dinamis) menautkan itu modul. Kadang-kadang paket dijalankan ./configureuntuk mengidentifikasi ketersediaan ketergantungan tersebut dan jika perpustakaan tidak ada, (atau pengguna tidak ingin mengaktifkan) fungsionalitas tersebut dinonaktifkan secara otomatis tanpa menghubungkan dengan perpustakaan itu. Ini selalu bermanfaat jika arahan ini dibuat secara otomatis.

  4. Perizinan:
    Salah satu contoh contoh preprocessor directive yang sangat menarik adalah ffmpeg . Ini memiliki codec yang berpotensi melanggar paten dengan penggunaannya. Jika Anda mengunduh sumber dan kompilasi untuk menginstal, ia menanyakan apakah Anda ingin atau menyimpan hal-hal seperti itu. Menyembunyikan kode di bawah beberapa jika kondisi masih bisa mendarat Anda di pengadilan!

  5. Copy-paste kode:
    Aka macro. Ini bukan saran untuk menggunakan makro secara berlebihan - hanya saja makro memiliki cara yang jauh lebih kuat untuk menerapkan padanan salinan di masa lalu . Tapi gunakan dengan hati-hati; dan gunakan jika Anda tahu apa yang Anda lakukan. Konstanta tentu saja tidak dapat melakukan ini. Tetapi seseorang dapat menggunakan inlinefungsi juga jika itu mudah dilakukan.

Jadi kapan Anda menggunakan konstanta?
Hampir di tempat lain.

  1. Aliran kode yang lebih rapi:
    Secara umum, ketika Anda menggunakan konstanta, hampir tidak dapat dibedakan dari variabel reguler dan karenanya, itu adalah kode yang lebih mudah dibaca. Jika Anda menulis rutin yaitu 75 baris - memiliki 3 atau 4 baris setelah setiap 10 baris dengan #ifdef SANGAT tidak dapat dibaca . Mungkin diberikan konstanta primer yang diatur oleh #ifdef, dan menggunakannya dalam aliran alami di mana saja.

  2. Kode yang terindentasi dengan baik: Semua arahan preprosesor, tidak pernah bekerja dengan baik dengan kode yang diindentasi dengan baik . Bahkan jika Anda compiler tidak memungkinkan lekukan dari #def, Pre-ANSI C preprocessor tidak memungkinkan untuk ruang antara awal baris dan "#" karakter; "#" yang memimpin harus selalu ditempatkan di kolom pertama.

  3. Konfigurasi:
    Alasan lain mengapa konstanta / atau variabel masuk akal adalah bahwa mereka dapat dengan mudah berevolusi dari yang tertaut ke global atau di masa depan dapat diperluas untuk diturunkan dari file konfigurasi.

Satu hal terakhir:
Jangan pernah gunakan arahan preprocessor #ifdefuntuk #endif melintasi ruang lingkup atau { ... }. yaitu mulai #ifdefatau berakhir #endifpada sisi yang berbeda { ... }. Ini sangat buruk; itu bisa membingungkan, terkadang berbahaya.

Ini tentu saja, bukan daftar lengkap, tetapi menunjukkan Anda perbedaan nyata, di mana metode mana yang lebih tepat untuk digunakan. Ini tidak benar-benar tentang mana yang lebih baik , itu selalu lebih mana yang lebih alami untuk digunakan dalam konteks tertentu.

Dipan Mehta
sumber
Terima kasih atas jawaban yang panjang dan terperinci. Saya menyadari sebagian besar hal yang Anda sebutkan dan dihilangkan dari pertanyaan karena pertanyaannya bukan apakah menggunakan kemampuan preprosesor sama sekali, tetapi tentang jenis penggunaan tertentu. Tapi saya pikir poin dasar yang Anda buat adalah benar.
MByD
1
"Jangan pernah MENGGUNAKAN arahan preprocessor # jika untuk # endif melintasi ruang lingkup atau {...}" itu tergantung pada IDE. Jika IDE mengenali blok preprosesor yang tidak aktif dan meruntuhkannya atau menunjukkannya dengan warna yang berbeda, itu tidak membingungkan.
Abyx
@Abyx memang benar bahwa IDE dapat membantu. Namun, dalam banyak kasus di mana orang mengembangkan di bawah platform * nix (linux dll) - pilihan editor dll banyak (VI, emacs dan sebagainya). Jadi orang lain mungkin menggunakan editor yang berbeda dari milik Anda.
Dipan Mehta
4

Apakah pelanggan Anda memiliki akses ke kode Anda? Jika ya, maka preprosesor bisa menjadi pilihan yang lebih baik. Kami memiliki sesuatu yang serupa dan kami menggunakan flag waktu kompilasi untuk berbagai pelanggan (atau fitur khusus pelanggan). Kemudian skrip dapat mengekstrak kode khusus pelanggan dan kami mengirimkan kode itu. Pelanggan tidak mengetahui pelanggan lain atau fitur khusus pelanggan lainnya.

Aditya Sehgal
sumber
3

Beberapa poin tambahan yang layak disebutkan:

  1. Compiler dapat memproses beberapa jenis ekspresi konstan yang preprocessor tidak dapat. Sebagai contoh, preprocessor umumnya tidak memiliki cara untuk mengevaluasi sizeof()pada tipe non-primitif. Ini mungkin memaksa penggunaan if()dalam beberapa kasus.

  2. Kompiler akan mengabaikan sebagian besar masalah sintaksis dalam kode yang dilompati #if(beberapa masalah terkait preprosesor masih dapat menyebabkan masalah) tetapi bersikeras pada kebenaran sintaksis untuk kode yang dilewati if(). Jadi, jika kode yang dilompati untuk beberapa build tetapi tidak yang lain menjadi tidak valid sebagai akibat dari perubahan di tempat lain dalam file (mis. Pengidentifikasi yang diganti nama) itu umumnya akan mengomel pada semua build jika dinonaktifkan dengan if()tetapi tidak jika dinonaktifkan oleh #if. Tergantung pada mengapa kode dilewati, itu mungkin atau mungkin bukan hal yang baik.

  3. Beberapa kompiler akan menghilangkan kode yang tidak terjangkau sementara yang lain tidak; deklarasi durasi penyimpanan statis dalam kode yang tidak dapat dijangkau, termasuk string literal, dapat mengalokasikan ruang meskipun tidak ada kode yang dapat menggunakan ruang yang dialokasikan demikian.

  4. Makro dapat menggunakan if()tes di dalamnya, tetapi sayangnya tidak ada mekanisme bagi preprosesor untuk melakukan logika kondisional dalam makro [perhatikan bahwa untuk digunakan dalam makro, ? :operator mungkin sering lebih baik daripada if, tetapi prinsip yang sama berlaku].

  5. The #ifdirektif dapat digunakan untuk mengontrol macro apa yang bisa didefinisikan.

Saya pikir if()seringkali lebih bersih untuk sebagian besar kasus kecuali yang melibatkan pengambilan keputusan berdasarkan apa yang digunakan kompiler, atau makro apa yang harus didefinisikan; beberapa masalah di atas mungkin mengharuskan penggunaan #if, meskipun demikian, bahkan dalam kasus di mana ifakan tampak lebih bersih.

supercat
sumber
1

Singkat cerita: kekhawatiran tentang arahan preprosesor pada dasarnya adalah 2, beberapa inklusi dan mungkin kode yang lebih mudah dibaca dan logika yang lebih criptic.

Jika proyek Anda kecil dengan hampir tidak ada rencana besar untuk masa depan, saya pikir kedua opsi menawarkan rasio yang sama antara pro dan kontra, tetapi jika proyek Anda akan sangat besar, pertimbangkan hal lain seperti penulisan file header terpisah jika Anda masih ingin menggunakan arahan preprosesor, tetapi perlu diingat bahwa pendekatan semacam ini biasanya membuat kode kurang mudah dibaca dan pastikan bahwa nama konstanta itu berarti sesuatu untuk logika bisnis dari program itu sendiri.

Mikro
sumber
Ini adalah basis kode yang hebat, dan definisi dalam beberapa header yang terpisah memang.
MByD