#ifdef vs #if - mana yang lebih baik / lebih aman sebagai metode untuk mengaktifkan / menonaktifkan kompilasi bagian kode tertentu?


112

Ini mungkin masalah gaya, tetapi ada sedikit perbedaan dalam tim pengembang kami dan saya bertanya-tanya apakah ada orang lain yang punya ide tentang masalah ini ...

Pada dasarnya, kami memiliki beberapa pernyataan cetak debug yang kami matikan selama pengembangan normal. Secara pribadi saya lebih suka melakukan hal berikut:

//---- SomeSourceFile.cpp ----

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

#if(DEBUG_ENABLED)
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

Beberapa tim lebih menyukai yang berikut ini:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

#ifdef DEBUG_ENABLED
    printf("Debugging: someVariable == %d", someVariable);
#endif
}

... metode mana yang terdengar lebih baik untuk Anda dan mengapa? Perasaan saya adalah yang pertama lebih aman karena selalu ada sesuatu yang didefinisikan dan tidak ada bahaya bisa menghancurkan definisi lain di tempat lain.


Catatan: dengan #if, Anda juga dapat menggunakan #elifsecara konsisten, tidak seperti dengan #ifdef. Jadi, alih-alih hanya menggunakan #define BLAH, gunakan #define BLAH 1dengan #if BLAH, dll ...
Andrew

Jawaban:


82

Reaksi awal saya #ifdef, tentu saja , tetapi saya pikir #ifsebenarnya memiliki beberapa keuntungan signifikan untuk ini - inilah alasannya:

Pertama, Anda dapat menggunakan DEBUG_ENABLEDpreprocessor dan tes terkompilasi. Contoh - Seringkali, saya ingin waktu tunggu yang lebih lama saat debug diaktifkan, jadi dengan menggunakan #if, saya bisa menulis ini

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... dari pada ...

#ifdef DEBUG_MODE
  DoSomethingSlowWithTimeout(5000);
#else
  DoSomethingSlowWithTimeout(1000);
#endif

Kedua, Anda berada dalam posisi yang lebih baik jika Anda ingin bermigrasi dari #definekonstanta global ke konstanta global. #defines biasanya disukai oleh sebagian besar programmer C ++.

Dan, Ketiga, Anda mengatakan Anda memiliki perbedaan dalam tim Anda. Dugaan saya adalah ini berarti anggota yang berbeda telah mengadopsi pendekatan yang berbeda, dan Anda perlu membuat standar. Aturan yang #ifmerupakan pilihan yang lebih disukai berarti bahwa kode yang digunakan #ifdefakan mengkompilasi -dan dijalankan- bahkan ketika DEBUG_ENABLEDsalah. Dan jauh lebih mudah untuk melacak dan menghapus keluaran debug yang dihasilkan saat tidak semestinya daripada sebaliknya.

Oh, dan poin keterbacaan kecil. Anda harus dapat menggunakan true / false daripada 0/1 di Anda #define, dan karena nilainya adalah token leksikal tunggal, ini adalah satu-satunya saat Anda tidak memerlukan tanda kurung di sekitarnya.

#define DEBUG_ENABLED true

dari pada

#define DEBUG_ENABLED (1)

Konstanta mungkin tidak digunakan untuk mengaktifkan / menonaktifkan debugging, jadi memicu #ifdef dengan #define to 0 bisa jadi tidak terlalu berbahaya. Adapun benar / salah, itu ditambahkan di C99 dan tidak ada di C89 / C90.
Michael Carman

Micheal: Dia menganjurkan untuk tidak menggunakan #ifdef?!
Jon Cage

7
Ya, satu masalah #ifdefadalah bahwa ia bekerja dengan hal-hal yang tidak ditentukan; apakah mereka tidak didefinisikan dengan sengaja atau karena salah ketik atau apa pun yang Anda alami.
bames53

6
Penambahan Anda pada jawabannya salah. #if DEBUG_ENBALEDbukan kesalahan yang terdeteksi oleh preprocessor. Jika DEBUG_ENBALEDtidak ditentukan, itu diperluas ke token 0dalam #ifarahan.
R .. GitHub STOP HELPING ICE

6
@R .. Di banyak kompiler Anda dapat mengaktifkan peringatan untuk "#if DEBUG_ENABLED" saat DEBUG_ENABLED tidak ditentukan. Dalam GCC gunakan "-Wundef". Di Microsoft Visual Studio gunakan "/ w14668" untuk mengaktifkan C4668 sebagai peringatan level 1.
Akankah

56

Keduanya mengerikan. Sebaliknya, lakukan ini:

#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif

Kemudian kapan pun Anda membutuhkan kode debug, masukkan ke dalamnya D();. Dan program Anda tidak tercemar dengan labirin yang mengerikan #ifdef.


6
@Bayu_joo Sebenarnya, menurut saya versi aslinya baik-baik saja. Titik koma akan diartikan sebagai pernyataan kosong. Namun, melupakan titik koma bisa membuatnya berbahaya.
Casey Kuball

31

#ifdef hanya memeriksa apakah token ditentukan, diberikan

#define FOO 0

kemudian

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"

20

Kami mengalami masalah yang sama di beberapa file dan selalu ada masalah dengan orang-orang yang lupa menyertakan file "tanda fitur" (Dengan basis kode> 41.000 file, hal ini mudah dilakukan).

Jika Anda memiliki fitur.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Tapi kemudian Anda lupa menyertakan file header di file.cpp:

#if COOL_FEATURE
    // definitely awesome stuff here...
#endif

Kemudian Anda mendapat masalah, kompilator menafsirkan COOL_FEATURE tidak didefinisikan sebagai "salah" dalam kasus ini dan gagal untuk memasukkan kode. Ya, gcc mendukung penandaan yang menyebabkan kesalahan untuk makro yang tidak ditentukan ... tetapi sebagian besar kode pihak ke-3 menentukan atau tidak menentukan fitur, jadi ini tidak akan portabel.

Kami telah mengadopsi cara portabel untuk mengoreksi kasus ini serta menguji status fitur: makro fungsi.

jika Anda mengubah fitur di atas. h menjadi:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE() 1

#endif // FEATURE_H

Tapi kemudian Anda lagi lupa menyertakan file header di file.cpp:

#if COOL_FEATURE()
    // definitely awseome stuff here...
#endif

Praprosesor akan mengalami error karena penggunaan makro fungsi yang tidak ditentukan.


17

Untuk keperluan melakukan kompilasi bersyarat, #if dan #ifdef hampir sama, tetapi tidak sepenuhnya. Jika kompilasi bersyarat Anda bergantung pada dua simbol, maka #ifdef tidak akan berfungsi dengan baik. Misalnya, Anda memiliki dua simbol kompilasi bersyarat, PRO_VERSION dan TRIAL_VERSION, Anda mungkin memiliki sesuatu seperti ini:

#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif

Menggunakan #ifdef di atas menjadi jauh lebih rumit, terutama membuat bagian #else berfungsi.

Saya mengerjakan kode yang menggunakan kompilasi kondisional secara ekstensif dan kami memiliki campuran #if & #ifdef. Kita cenderung menggunakan # ifdef / # ifndef untuk kasus sederhana dan #if setiap kali dua atau lebih simbol sedang dievaluasi.


1
dalam #if definedapa defineditu kata kunci atau?
nmxprime

15

Saya pikir ini sepenuhnya soal gaya. Tidak ada yang benar-benar memiliki keunggulan dibandingkan yang lain.

Konsistensi lebih penting daripada pilihan tertentu, jadi saya sarankan Anda berkumpul dengan tim Anda dan memilih satu gaya, dan menaatinya.


8

Saya sendiri lebih suka:

#if defined(DEBUG_ENABLED)

Karena lebih mudah untuk membuat kode yang mencari kondisi sebaliknya lebih mudah dikenali:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)

8
Secara pribadi saya pikir lebih mudah melewatkan tanda seru kecil itu!
Jon Cage

6
Dengan penyorotan sintaks? :) Dalam penyorotan sintaks, "n" dalam "ifndef" jauh lebih sulit dikenali karena warnanya sama.
Jim Buck

Oke, maksud saya #ifndef lebih mudah dikenali daripada #if! Didefinisikan ketika Anda membandingkan dengan #if ditentukan .. tetapi mengingat semua #if ditentukan / # if! Ditentukan vs # ifdef / # ifndef, keduanya sama-sama tidak dapat dibaca!
Jon Cage

@JonCage Saya tahu sudah beberapa tahun sejak komentar ini, tetapi saya ingin menunjukkan bahwa Anda dapat menulisnya #if ! definedagar !lebih menonjol dan sulit untuk dilewatkan.
Pharap

@Pharap - Itu pasti terlihat seperti peningkatan :)
Jon Cage

7

Ini masalah gaya. Tetapi saya merekomendasikan cara yang lebih ringkas untuk melakukan ini:

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif

debug_print("i=%d\n", i);

Anda melakukan ini sekali, lalu selalu gunakan debug_print () untuk mencetak atau tidak melakukan apa pun. (Ya, ini akan terkompilasi dalam kedua kasus.) Dengan cara ini, kode Anda tidak akan dirusak dengan arahan preprocessor.

Jika Anda mendapatkan peringatan "ekspresi tidak berpengaruh" dan ingin menyingkirkannya, berikut adalah alternatifnya:

void dummy(const char*, ...)
{}

#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif

debug_print("i=%d\n", i);

3
Mungkin makro pencetakan bukanlah contoh terbaik - kami sebenarnya sudah melakukan ini di basis kode kami untuk kode debug yang lebih standar. Kami menggunakan bit #if / #ifdefined untuk area di mana Anda mungkin ingin mengaktifkan debugging ekstra ..
Jon Cage

5

#ifmemberi Anda opsi untuk menyetelnya ke 0 untuk mematikan fungsionalitas, sambil tetap mendeteksi bahwa sakelar itu ada.
Secara pribadi saya selalu #define DEBUG 1jadi saya bisa menangkapnya dengan #if atau #ifdef


1
Ini gagal, karena #define DEBUG = 0 sekarang tidak akan berjalan #if tetapi akan menjalankan #ifdef
tloach

1
Itulah intinya, saya dapat menghapus DEBUG sepenuhnya atau hanya mengaturnya ke 0 untuk menonaktifkannya.
Martin Beckett

seharusnya begitu #define DEBUG 1. Tidak#define DEBUG=1
Keshava GN

4

#if dan #define MY_MACRO (0)

Menggunakan #if berarti Anda membuat makro "definisikan", yaitu sesuatu yang akan dicari di kode untuk diganti dengan "(0)". Ini adalah "neraka makro" yang saya benci untuk dilihat di C ++, karena mencemari kode dengan kemungkinan modifikasi kode.

Sebagai contoh:

#define MY_MACRO (0)

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

memberikan kesalahan berikut pada g ++:

main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|

Hanya satu kesalahan.

Artinya makro Anda berhasil berinteraksi dengan kode C ++ Anda: Panggilan ke fungsi berhasil. Dalam kasus sederhana ini, itu lucu. Tetapi pengalaman saya sendiri dengan makro yang bermain diam-diam dengan kode saya tidak penuh dengan kegembiraan dan kepenuhan, jadi ...

#ifdef dan #define MY_MACRO

Menggunakan #ifdef berarti Anda "mendefinisikan" sesuatu. Bukan berarti Anda memberinya nilai. Itu masih mencemari, tapi setidaknya, itu akan "digantikan oleh tidak ada", dan tidak dilihat oleh kode C ++ sebagai pernyataan kode yang tertinggal. Kode yang sama di atas, dengan definisi sederhana, itu:

#define MY_MACRO

int doSomething(int p_iValue)
{
   return p_iValue + 1 ;
}

int main(int argc, char **argv)
{
   int MY_MACRO = 25 ;
   doSomething(MY_MACRO) ;

   return 0;
}

Memberikan peringatan berikut:

main.cpp||In function int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|

Begitu...

Kesimpulan

Saya lebih suka hidup tanpa makro dalam kode saya, tetapi karena beberapa alasan (mendefinisikan pelindung header, atau makro debug), saya tidak bisa.

Tapi setidaknya, saya ingin membuatnya menjadi paling tidak interaktif dengan kode C ++ saya yang sah. Yang berarti menggunakan #define tanpa nilai, menggunakan #ifdef dan #ifndef (atau bahkan #if didefinisikan seperti yang disarankan oleh Jim Buck), dan yang terpenting, memberi mereka nama yang begitu lama dan begitu asing sehingga tidak ada orang waras yang akan menggunakannya itu "secara kebetulan", dan tidak akan mempengaruhi kode C ++ yang sah.

Posting Scriptum

Sekarang, saat saya membaca kembali posting saya, saya bertanya-tanya apakah saya seharusnya tidak mencoba menemukan beberapa nilai yang tidak akan pernah menjadi C ++ yang benar untuk ditambahkan ke definisi saya. Sesuatu seperti

#define MY_MACRO @@@@@@@@@@@@@@@@@@

yang dapat digunakan dengan #ifdef dan #ifndef, tetapi tidak membiarkan kode dikompilasi jika digunakan di dalam suatu fungsi ... Saya mencoba ini dengan sukses di g ++, dan itu memberikan kesalahan:

main.cpp|410|error: stray ‘@’ in program|

Menarik. :-)


Saya setuju bahwa makro bisa berbahaya, tetapi contoh pertama itu akan cukup jelas untuk di-debug dan tentu saja itu hanya memberikan satu kesalahan. Mengapa Anda mengharapkan lebih banyak? Saya telah melihat banyak kesalahan yang lebih buruk sebagai akibat dari makro ...
Jon Cage

Memang benar perbedaan antara satu solusi dan lainnya hampir sepele. Tetapi dalam hal ini, karena kita berbicara tentang dua gaya pengkodean yang bersaing, maka bahkan yang sepele pun tidak dapat diabaikan, karena setelah itu, yang tersisa hanyalah selera pribadi (dan pada titik itu, saya percaya itu tidak boleh dinormalisasi )
paercebal

4

Itu sama sekali bukan masalah gaya. Juga pertanyaannya sayangnya salah. Anda tidak dapat membandingkan arahan preprocessor ini dalam arti lebih baik atau lebih aman.

#ifdef macro

berarti "jika makro ditentukan" atau "jika makro ada". Nilai makro tidak menjadi masalah di sini. Bisa apa saja.

#if macro

jika selalu dibandingkan dengan suatu nilai. Dalam contoh di atas, ini adalah perbandingan implisit standar:

#if macro !=0

contoh untuk penggunaan #if

#if CFLAG_EDITION == 0
    return EDITION_FREE;
#elif CFLAG_EDITION == 1
    return EDITION_BASIC;
#else
    return EDITION_PRO;
#endif

Anda sekarang dapat meletakkan definisi CFLAG_EDITION baik di kode Anda

#define CFLAG_EDITION 1 

atau Anda dapat mengatur makro sebagai bendera kompilator. Lihat juga di sini .


2

Yang pertama tampak lebih jelas bagi saya. Tampaknya lebih alami menjadikannya sebagai bendera dibandingkan dengan didefinisikan / tidak ditentukan.


2

Keduanya sama persis. Dalam penggunaan idiomatik, #ifdef digunakan hanya untuk memeriksa definisi (dan apa yang saya gunakan dalam contoh Anda), sedangkan #if digunakan dalam ekspresi yang lebih kompleks, seperti #if ditentukan (A) &&! Didefinisikan (B).


1
OP tidak menanyakan mana yang lebih baik antara "#ifdef" dan "#if ditentukan" melainkan antara "# ifdef / # jika ditentukan" dan "#if".
betis

1

Sedikit OT, tetapi menyalakan / mematikan logging dengan preprocessor jelas kurang optimal di C ++. Ada alat logging yang bagus seperti Apache's log4cxx yang merupakan open-source dan tidak membatasi cara Anda mendistribusikan aplikasi. Mereka juga memungkinkan Anda untuk mengubah level logging tanpa kompilasi ulang, memiliki overhead yang sangat rendah jika Anda mematikan logging, dan memberi Anda kesempatan untuk sepenuhnya mematikan logging dalam produksi.


1
Saya setuju, dan kami benar-benar melakukannya dalam kode kami, saya hanya ingin contoh sesuatu yang mungkin Anda gunakan #if dll. Untuk
Jon Cage

1

Saya biasa menggunakan #ifdef , tetapi ketika saya beralih ke Doxygen untuk dokumentasi, saya menemukan bahwa makro yang dikomentari tidak dapat didokumentasikan (atau, setidaknya, Doxygen menghasilkan peringatan). Ini berarti saya tidak dapat mendokumentasikan makro pengalih fitur yang saat ini tidak diaktifkan.

Meskipun dimungkinkan untuk menentukan makro hanya untuk Doxygen, ini berarti bahwa makro di bagian kode yang tidak aktif akan didokumentasikan juga. Saya pribadi ingin menunjukkan sakelar fitur dan sebaliknya hanya mendokumentasikan apa yang saat ini dipilih. Lebih jauh lagi, membuat kodenya cukup berantakan jika ada banyak macro yang harus didefinisikan hanya saat Doxygen memproses file tersebut.

Oleh karena itu, dalam kasus ini, lebih baik untuk selalu mendefinisikan makro dan penggunaan #if.


0

Saya selalu menggunakan tanda #ifdef dan compiler untuk mendefinisikannya ...


Ada alasan tertentu (karena penasaran)?
Jon Cage

2
Sejujurnya saya tidak pernah memikirkannya - bagaimana hal itu dilakukan di tempat-tempat saya bekerja. Ini memberikan keuntungan bahwa alih-alih membuat perubahan kode untuk build produksi, yang harus Anda lakukan adalah 'membuat DEBUG' untuk debug, atau 'membuat PRODUCTION' untuk reguler
tloach

0

Alternatifnya, Anda bisa mendeklarasikan konstanta global, dan menggunakan C ++ if, sebagai ganti preprocessor #if. Kompilator harus mengoptimalkan cabang yang tidak terpakai untuk Anda, dan kode Anda akan lebih bersih.

Inilah yang dikatakan C ++ Gotchas oleh Stephen C. Dewhurst tentang penggunaan # if.


1
Solusi yang buruk, ada beberapa masalah berikut ini: 1. Hanya berfungsi di fungsi, Anda tidak dapat menghapus variabel kelas yang tidak diperlukan, dll. 2. Kompiler mungkin memberikan peringatan tentang kode yang tidak dapat dijangkau 3. Kode dalam if masih perlu dikompilasi, yang artinya Anda harus menjaga agar semua fungsi debug Anda ditentukan, dll.
Don Neufeld

Pertama, pertanyaannya secara khusus tentang debug printfs, jadi variabel kelas yang tidak dibutuhkan tidak menjadi masalah di sini. Kedua, mengingat kemampuan kompiler modern, Anda harus menggunakan #ifdefs sesedikit mungkin. Dalam kebanyakan kasus, Anda dapat menggunakan konfigurasi build atau spesialisasi template.
Dima

0

Ada perbedaan dalam hal cara berbeda untuk menentukan definisi bersyarat untuk driver:

diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )

keluaran:

344c344
< #define A 
---
> #define A 1

Artinya, itu -DAadalah sinonim untuk -DA=1dan jika nilai dihilangkan, maka hal itu dapat menyebabkan masalah jika #if Adigunakan.


0

Saya suka #define DEBUG_ENABLED (0)ketika Anda mungkin menginginkan beberapa level debug. Sebagai contoh:

#define DEBUG_RELEASE (0)
#define DEBUG_ERROR (1)
#define DEBUG_WARN (2)
#define DEBUG_MEM (3)
#ifndef DEBUG_LEVEL
#define DEBUG_LEVEL (DEBUG_RELEASE)
#endif
//...

//now not only
#if (DEBUG_LEVEL)
//...
#endif

//but also
#if (DEBUG_LEVEL >= DEBUG_MEM)
LOG("malloc'd %d bytes at %s:%d\n", size, __FILE__, __LINE__);
#endif

Mempermudah untuk men-debug kebocoran memori, tanpa semua baris log itu menghalangi Anda untuk men-debug hal-hal lain.

Juga di #ifndefsekitar define membuatnya lebih mudah untuk memilih level debug tertentu pada commandline:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Jika bukan karena ini, saya akan memberikan keuntungan #ifdefkarena bendera compiler / make akan diganti oleh yang ada di file. Jadi Anda tidak perlu khawatir tentang mengubah kembali header sebelum melakukan komit.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.