#ifdef vs #if - kodun belirli bölümlerinin derlenmesini etkinleştirmek / devre dışı bırakmak için bir yöntem olarak hangisi daha iyi / daha güvenli?


112

Bu bir tarz meselesi olabilir, ancak geliştirme ekibimizde bir miktar bölünme var ve bu konuda başka birinin fikri olup olmadığını merak ettim ...

Temel olarak, normal geliştirme sırasında kapattığımız bazı hata ayıklama yazdırma ifadelerimiz var. Şahsen şunları yapmayı tercih ederim:

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

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

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

Takımın bazıları aşağıdakileri tercih ediyor:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

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

... bu yöntemlerden hangisi size daha iyi geliyor ve neden? Benim hissim, ilkinin daha güvenli olduğu, çünkü her zaman tanımlanmış bir şey olduğu ve başka yerlerdeki diğer tanımları yok etme tehlikesi olmadığı yönünde.


Not: ile #if, #elifile aksine, tutarlı bir şekilde de kullanabilirsiniz #ifdef. Böylece, bunun yerine sadece kullanmanın #define BLAHkullanımı, #define BLAH 1ile #if BLAH... vs,
Andrew

Yanıtlar:


82

İlk tepkim #ifdefelbette oldu , ancak bunun için #ifaslında bazı önemli avantajları olduğunu düşünüyorum - işte nedeni:

Birincisi, DEBUG_ENABLEDönişlemci ve derlenmiş testlerde kullanabilirsiniz. Örnek - Genellikle, hata ayıklama etkinleştirildiğinde daha uzun zaman aşımları olmasını isterim, bu yüzden kullanarak #ifbunu yazabilirim

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... onun yerine ...

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

İkincisi, a'dan #defineküresel bir sabite geçmek istiyorsanız daha iyi bir konumdasınız. #define'ler çoğu C ++ programcısı tarafından genellikle hoş karşılanmaz.

Üçüncüsü, takımınızda bir bölünme olduğunu söylüyorsunuz. Tahminimce bu, farklı üyelerin halihazırda farklı yaklaşımlar benimsediği ve standartlaştırmanız gerektiği anlamına geliyor. Bunun #iftercih edilen seçim olduğuna karar vermek, kod kullanmanın yanlış #ifdefolsa bile derleyeceği ve çalıştıracağı anlamına gelir DEBUG_ENABLED. Ve tam tersi olmaması gerektiğinde üretilen hata ayıklama çıktısını izlemek ve kaldırmak çok daha kolaydır.

Oh, ve küçük bir okunabilirlik noktası. Sizin için 0/1 yerine doğru / yanlış kullanabilmelisiniz #defineve değer tek bir sözlü belirteç olduğundan, etrafında parantezlere ihtiyaç duymadığınız tek zamandır.

#define DEBUG_ENABLED true

onun yerine

#define DEBUG_ENABLED (1)

Sabit, hata ayıklamayı etkinleştirmek / devre dışı bırakmak için kullanılmayabilir, bu nedenle bir #ifdef'i #define ile 0 olarak tetiklemek o kadar da zararsız olmayabilir. Doğru / yanlışa gelince, bunlar C99'a eklendi ve C89 / C90'da mevcut değil.
Michael Carman

Micheal: #ifdef kullanımına karşı mı savundu ?!
Jon Cage

7
Evet, bir sorun, #ifdeftanımlanmamış şeylerle çalışmasıdır; kasıtlı olarak tanımlanmadıkları veya bir yazım hatası yüzünden mi yoksa sizde mi var.
bames53

6
Cevaba eklemeniz yanlış. #if DEBUG_ENBALEDönişlemci tarafından tespit edilen bir hata değildir. Eğer DEBUG_ENBALEDtanımlı değil, bu belirteci şekilde genişler 0içinde #ifdirektifleri.
R .. GitHub BUZA YARDIM ETMEYİ DURDUR

6
@R .. Birçok derleyicide, DEBUG_ENABLED tanımlı olmadığında "# eğer DEBUG_ENABLED" için bir uyarı etkinleştirebilirsiniz. GCC'de "-Wundef" kullanın. Microsoft Visual Studio'da C4668'i 1. düzey uyarı olarak açmak için "/ w14668" kullanın.
Will

56

İkisi de çirkin. Bunun yerine şunu yapın:

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

Sonra ne zaman hata ayıklama koduna ihtiyacınız olursa, içine koyun D();. Ve programınız korkunç labirentlerle kirlenmemiş #ifdef.


6
@MatthieuM. Aslında orijinal versiyonun iyi olduğunu düşünüyorum. Noktalı virgül boş bir ifade olarak yorumlanacaktır. Ancak noktalı virgülün unutulması onu tehlikeli hale getirebilir.
Casey Kuball

31

#ifdef sadece bir belirteç tanımlanıp tanımlanmadığını kontrol eder,

#define FOO 0

sonra

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

20

Aynı sorunu birden çok dosyada yaşadık ve her zaman bir "özellik işareti" dosyası eklemeyi unutan kişilerde sorun vardır (41.000'den fazla dosyadan oluşan bir kod tabanıyla bunu yapmak kolaydır).

Feature.h'iniz varsa:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Ama sonra başlık dosyasını file.cpp'ye eklemeyi unuttunuz:

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

O zaman bir sorununuz varsa, derleyici bu durumda COOL_FEATURE değerini tanımsız olarak "yanlış" olarak yorumlar ve kodu dahil edemez. Evet gcc, tanımlanmamış makrolar için hataya neden olan bir bayrağı destekler ... ancak çoğu 3. taraf kodu, özellikleri tanımlar veya tanımlamaz, bu nedenle bu taşınabilir olmaz.

Bu durum için taşınabilir bir düzeltme yöntemi benimsedik ve bir özelliğin durumu: işlev makrolarını test ettik.

yukarıdaki feature.h dosyasını şu şekilde değiştirdiyseniz:

#ifndef FEATURE_H
#define FEATURE_H

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

#endif // FEATURE_H

Ama sonra başlık dosyasını file.cpp'ye eklemeyi tekrar unuttunuz:

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

Önişlemci, tanımsız bir işlev makrosu kullanılması nedeniyle hata yapardı.


17

Koşullu derleme gerçekleştirmek için #if ve #ifdef hemen hemen aynıdır, ancak tam olarak değil. Koşullu derlemeniz iki sembole bağlıysa, #ifdef de çalışmayacaktır. Örneğin, PRO_VERSION ve TRIAL_VERSION olmak üzere iki koşullu derleme sembolünüz olduğunu varsayalım, şuna benzer bir şeye sahip olabilirsiniz:

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

#İfdef kullanmak, özellikle #else parçasını çalıştırmak çok daha karmaşık hale gelir.

Yoğun olarak koşullu derlemeyi kullanan kod üzerinde çalışıyorum ve bir #if & #ifdef karışımına sahibiz. Basit durum için # ifdef / # ifndef ve iki veya daha fazla sembol değerlendirilirken #if kullanma eğilimindeyiz.


1
içinde #if definedne olduğunu definedbir anahtar kelime veya nedir?
nmxprime

15

Bence bu tamamen bir tarz meselesi. İkisinin de diğerine göre bariz bir avantajı yok.

Tutarlılık her iki seçenekten daha önemlidir, bu yüzden ekibinizle bir araya gelip bir stil seçmenizi ve ona bağlı kalmanızı öneririm.


8

Ben kendim tercih ederim:

#if defined(DEBUG_ENABLED)

Ters koşulu arayan kod oluşturmayı çok daha kolay hale getirdiği için tespit edilmesi çok daha kolay:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)

8
Şahsen bu küçük ünlem işaretini kaçırmanın daha kolay olduğunu düşünüyorum!
Jon Cage

6
Sözdizimi vurgulamalı mı? :) Sözdizimi vurgulamada, "ifndef" deki "n" nin hepsi aynı renk olduğu için fark edilmesi çok daha zordur.
Jim Buck

Pekala, # ifndef'in, # if tanımlıysa # if! Tanımlı olmaktan daha kolay olduğunu söyledim .. ama # if tanımlı / # if!
Jon Cage

Ben Bu yorumun beri birkaç yıl oldu biliyorum ama siz bunu yazabilir işaret etmek istiyorum @JonCage #if ! definedyapmak !kaçıran daha belirgin ve sert.
Pharap

@Pharap - Bu kesinlikle bir gelişme gibi görünüyor :)
Jon Cage

7

Bu bir tarz meselesi. Ancak bunu yapmanın daha kısa bir yolunu öneriyorum:

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

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

Bunu bir kez yaparsınız, ardından yazdırmak veya hiçbir şey yapmamak için her zaman debug_print () kullanın. (Evet, bu her iki durumda da derlenecektir.) Bu şekilde, kodunuz önişlemci yönergeleriyle karıştırılmayacaktır.

"İfadenin etkisi yok" uyarısını alırsanız ve bundan kurtulmak istiyorsanız, işte bir alternatif:

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

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

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

3
Belki de yazdırma makrosu en iyi örnek değildi - aslında bunu daha standart hata ayıklama kodumuz için kod tabanımızda yapıyoruz. Ekstra hata ayıklamayı açmak isteyebileceğiniz alanlar için #if / # tanımlı bitleri kullanıyoruz ..
Jon Cage

5

#ifanahtarın hala orada olduğunu tespit ederken işlevselliği kapatmak için bunu 0'a ayarlama seçeneği sunar.
Şahsen ben her zaman #define DEBUG 1bu yüzden onu bir #if veya #ifdef ile yakalayabilirim


1
Bu başarısız olur, çünkü #define DEBUG = 0 artık #if çalışmayacaktır ancak #ifdef
tloach

1
Konu bu, DEBUG'ı tamamen kaldırabilirim veya devre dışı bırakmak için 0 olarak ayarlayabilirim.
Martin Beckett

olması gerekir #define DEBUG 1. Değil#define DEBUG=1
Keshava GN

4

#if ve # tanımla MY_MACRO (0)

#İf kullanılması, bir "tanımla" makrosu oluşturduğunuz anlamına gelir, yani kodda "(0)" ile değiştirilecek bir şey aranır. Bu, C ++ 'da görmekten nefret ettiğim "makro cehennemi", çünkü kodu potansiyel kod değişiklikleriyle kirletiyor.

Örneğin:

#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;
}

g ++ üzerinde şu hatayı verir:

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

Yalnızca bir hata.

Bu, makronuzun C ++ kodunuzla başarılı bir şekilde etkileşime girdiği anlamına gelir: İşleve yapılan çağrı başarılı oldu. Bu basit durumda, eğlenceli. Ancak, kodumla sessizce oynayan makrolarla ilgili kendi deneyimim neşe ve eksiksizlik dolu değil, bu yüzden ...

#ifdef ve #define MY_MACRO

#İfdef kullanmak, bir şeyi "tanımladığınız" anlamına gelir. Ona bir değer verdiğinden değil. Hala kirletiyor, ancak en azından "hiçbir şeyle değiştirilecek" ve C ++ kodu tarafından gecikmeli kod ifadesi olarak görülmeyecek. Yukarıdaki aynı kod, basit bir tanımla:

#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;
}

Aşağıdaki uyarıları verir:

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 ===|

Yani...

Sonuç

Kodumda makrolar olmadan yaşamayı tercih ederim, ancak birden çok nedenden dolayı (başlık korumalarını tanımlamak veya makrolarda hata ayıklamak) yapamam.

Ama en azından, meşru C ++ kodumla onları mümkün olan en az etkileşimli hale getirmeyi seviyorum. Bu, #define'ı değer olmadan kullanmak, #ifdef ve #ifndef kullanmak (veya Jim Buck tarafından önerildiği gibi # ise) ve hepsinden önemlisi, onlara çok uzun ve çok yabancı isimler vermek anlamına gelir. "tesadüfen" ve yasal C ++ kodunu hiçbir şekilde etkilemeyecek.

Yazı Yazısı

Şimdi, yazımı yeniden okurken, tanımıma eklemek için hiçbir zaman doğru C ++ olmayacak bir değer bulmaya çalışmamalı mıyım merak ediyorum. Gibi bir şey

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

#ifdef ve #ifndef ile kullanılabilir, ancak bir işlev içinde kullanıldığında kodun derlenmesine izin verilmez ... Bunu g ++ üzerinde başarıyla denedim ve hata verdi:

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

İlginç. :-)


Makroların tehlikeli olabileceğini kabul ediyorum, ancak bu ilk örneğin hata ayıklama açısından oldukça açık ve tabii ki yalnızca bir hata veriyor. Neden daha fazlasını bekliyorsunuz? Makroların bir sonucu olarak çok daha kötü hatalar gördüm ...
Jon Cage

Bir çözüm ile diğeri arasındaki farkın neredeyse önemsiz olduğu doğru. Ancak bu durumda, iki rakip kodlama stilinden bahsederken, önemsiz olanlar bile göz ardı edilemez çünkü ondan sonra geriye kalan tek şey kişisel zevktir (ve bu noktada normalleştirilmemesi gerektiğine inanıyorum. )
paercebal

4

Bu bir tarz meselesi değil. Ayrıca soru maalesef yanlış. Bu önişlemci direktiflerini daha iyi veya daha güvenli anlamında karşılaştıramazsınız.

#ifdef macro

"makro tanımlıysa" veya "makro varsa" anlamına gelir. Burada makronun değeri önemli değil. Her ne olabilir.

#if macro

her zaman bir değerle karşılaştırırsanız. Yukarıdaki örnekte standart örtük karşılaştırmadır:

#if macro !=0

#if kullanımına örnek

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

şimdi ya CFLAG_EDITION tanımını kodunuza koyabilirsiniz

#define CFLAG_EDITION 1 

veya makroyu derleyici bayrağı olarak ayarlayabilirsiniz. Ayrıca buraya bakın .


2

İlki bana daha net görünüyor. Tanımlanmış / tanımlanmamış ile karşılaştırıldığında onu bir bayrak yapmak daha doğal görünüyor.


2

Her ikisi de tam olarak eşdeğerdir. Deyimsel kullanımda, #ifdef yalnızca tanımlılığı (ve sizin örneğinizde kullanacağım şeyi) kontrol etmek için kullanılırken, #if, #if tanımlı (A) &&! Tanımlı (B) gibi daha karmaşık ifadelerde kullanılır.


1
OP "#ifdef" ve "# if tanımlı" arasında hangisinin daha iyi olduğunu sormuyordu, bunun yerine "# ifdef / # tanımlandıysa" ve "#if" arasında hangisinin daha iyi olduğunu soruyor.
incik

1

Biraz fazla OT, ancak önişlemci ile günlük kaydını açma / kapatma C ++ 'da kesinlikle yetersizdir. Apache'nin log4cxx gibi açık kaynak kodlu ve uygulamanızı nasıl dağıtacağınızı kısıtlamayan güzel günlüğe kaydetme araçları vardır . Ayrıca, yeniden derleme yapmadan günlük düzeylerini değiştirmenize, oturumu kapatırsanız ek yük çok düşük olmanıza ve üretimde oturumu tamamen kapatma şansı vermenize olanak tanır.


1
Katılıyorum ve bunu aslında kodumuzda yapıyoruz, sadece # if vb. İçin kullanabileceğiniz bir şeye örnek istedim
Jon Cage

1

Eskiden kullanırdım #ifdef, ancak dokümantasyon için Doxygen'e geçtiğimde, yorumlanmış makroların dokümante edilemediğini (veya en azından Doxygen bir uyarı verir) buldum. Bu, şu anda etkin olmayan özellik değiştirme makrolarını belgeleyemeyeceğim anlamına geliyor.

Makroları yalnızca Doxygen için tanımlamak mümkün olsa da, bu, kodun aktif olmayan kısımlarındaki makroların da belgeleneceği anlamına gelir. Ben şahsen özellik anahtarlarını göstermek ve aksi halde yalnızca şu anda seçili olanı belgelemek istiyorum. Ayrıca, yalnızca Doxygen dosyayı işlerken tanımlanması gereken birçok makro varsa, kodu oldukça karmaşık hale getirir.

Bu nedenle, bu durumda, her zaman makroları tanımlamak ve kullanmak daha iyidir #if.


0

Bunu tanımlamak için her zaman #ifdef ve derleyici bayraklarını kullandım ...


Herhangi bir özel neden (meraktan dolayı)?
Jon Cage

2
Dürüst olmak gerekirse bunu hiç düşünmedim - çalıştığım yerlerde nasıl yapıldığını. Bunun yerine yapmanız gereken tüm üretim yapı için bir kod değişikliği yapma düzenli için debug 'yapmak DEBUG' veya 'yapmak ÜRETİMİ' dir avantaj sağlayacak yapar
tloach

0

Alternatif olarak, bir genel sabit bildirebilir ve önişlemci #if yerine C ++ if kullanabilirsiniz. Derleyici, kullanılmayan dalları sizin için optimize etmelidir ve kodunuz daha temiz olacaktır.

İşte Stephen C'nin C ++ Gotchas'ın # if's kullanımı hakkında söylediği şey.


1
Bu berbat bir çözüm, şu problemleri var: 1. Yalnızca işlevlerde çalışır, gereksiz sınıf değişkenlerini kaldıramazsınız, vb. 2. Derleyiciler ulaşılamayan kodlar hakkında uyarılar atabilir 3. Eğer hala derlenmesi gerekiyorsa if içindeki kod, yani tüm hata ayıklama işlevlerinizi tanımlı tutmanız gerekir, vb.
Don Neufeld

İlk olarak soru özellikle printfs hatalarının ayıklanması hakkındaydı, bu nedenle gereksiz sınıf değişkenleri burada bir sorun değildir. İkinci olarak, modern derleyicilerin yetenekleri düşünüldüğünde #ifdefs'i olabildiğince az kullanmalısınız. Çoğu durumda bunun yerine yapılandırma yapılandırmalarını veya şablon uzmanlıklarını kullanabilirsiniz.
Dima

0

Sürücüye koşullu tanımlamanın farklı bir yolu olması durumunda bir fark vardır:

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

çıktı:

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

Bu, bunun -DAeşanlamlı olduğu anlamına gelir -DA=1ve eğer değer atlanırsa, #if Akullanım durumunda sorunlara yol açabilir .


0

#define DEBUG_ENABLED (0)Birden çok düzeyde hata ayıklama isteyebileceğiniz zamanlar hoşuma gidiyor . Örneğin:

#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

Diğer şeylerde hata ayıklama yolunuzda tüm bu günlük satırlarına sahip olmadan bellek sızıntılarında hata ayıklamayı kolaylaştırır.

Ayrıca #ifndeftanımın çevresi, komut satırında belirli bir hata ayıklama düzeyi seçmeyi kolaylaştırır:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Bunun için değilse #ifdef, derleyici / make bayrağı dosyadaki tarafından geçersiz kılınacağı için avantaj sağlayabilirim. Bu nedenle, commit yapmadan önce başlığı geri değiştirme konusunda endişelenmenize gerek yok.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.