#ifdef vs #if - co jest lepsze / bezpieczniejsze jako metoda włączania / wyłączania kompilacji poszczególnych sekcji kodu?

112

To może być kwestia stylu, ale w naszym zespole deweloperów jest trochę podziałów i zastanawiałem się, czy ktoś inny ma jakieś pomysły w tej sprawie ...

Zasadniczo mamy kilka instrukcji debugowania print, które wyłączamy podczas normalnego programowania. Osobiście wolę wykonać następujące czynności:

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

#define DEBUG_ENABLED (0)

...

SomeFunction()
{
    int someVariable = 5;

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

Niektórzy członkowie zespołu wolą jednak:

// #define DEBUG_ENABLED

...

SomeFunction()
{
    int someVariable = 5;

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

... która z tych metod brzmi dla Ciebie lepiej i dlaczego? Mam wrażenie, że pierwsza jest bezpieczniejsza, ponieważ zawsze jest coś zdefiniowanego i nie ma niebezpieczeństwa, że ​​mogłoby to zniszczyć inne definicje gdzie indziej.

Jon Cage
źródło
Uwaga: z #if, możesz również używać #elifw spójny sposób, w przeciwieństwie do with #ifdef. Dlatego zamiast po prostu używać #define BLAH, używaj #define BLAH 1z #if BLAH, itp ...
Andrew

Odpowiedzi:

82

Moja początkowa reakcja była #ifdefoczywiście , ale myślę, że #iffaktycznie ma to kilka znaczących zalet - oto dlaczego:

Po pierwsze, można użyć DEBUG_ENABLEDw testach preprocesora i skompilowanych. Przykład - Często potrzebuję dłuższych limitów czasu, gdy debugowanie jest włączone, więc używając #if, mogę to napisać

  DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);

... zamiast ...

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

Po drugie, jesteś w lepszej sytuacji, jeśli chcesz dokonać migracji ze #definestałej globalnej. #defineWiększość programistów C ++ patrzy z dezaprobatą.

Po trzecie, mówisz, że masz podział w swoim zespole. Domyślam się, że oznacza to, że różni członkowie przyjęli już różne podejścia i trzeba wprowadzić standaryzację. Reguła, która #ifjest preferowanym wyborem oznacza, że ​​kod używający #ifdefbędzie kompilował się - i działał - nawet wtedy, gdy DEBUG_ENABLEDjest fałszywy. I to znacznie łatwiej wytropić i usunąć wyjście debugowania, który powstaje, gdy nie powinno być, niż odwrotnie.

Aha, i drobny punkt dotyczący czytelności. Powinieneś móc użyć prawda / fałsz zamiast 0/1 w swoim #define, a ponieważ wartość jest pojedynczym tokenem leksykalnym, nie potrzebujesz nawiasów wokół niej.

#define DEBUG_ENABLED true

zamiast

#define DEBUG_ENABLED (1)
Roddy
źródło
Stała może nie być używana do włączania / wyłączania debugowania, więc wyzwolenie #ifdef z #define na 0 może nie być tak łagodne. Jeśli chodzi o prawda / fałsz, zostały one dodane w C99 i nie istnieją w C89 / C90.
Michael Carman,
Micheal: Opowiadał się przeciwko używaniu #ifdef ?!
Jon Cage,
7
Tak, jednym z problemów #ifdefjest to, że działa z rzeczami, które nie są zdefiniowane; czy nie zostały zdefiniowane celowo, czy z powodu literówki lub tego, co masz.
bames53
6
Twój dodatek do odpowiedzi jest błędny. #if DEBUG_ENBALEDnie jest błędem wykrytym przez preprocesor. Jeśli DEBUG_ENBALEDnie jest zdefiniowany, rozwija się do tokenu 0w #ifdyrektywach.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
6
@R .. W wielu kompilatorach można włączyć ostrzeżenie „#if DEBUG_ENABLED”, gdy DEBUG_ENABLED nie jest zdefiniowany. W GCC użyj "-Wundef". W programie Microsoft Visual Studio użyj „/ w14668”, aby włączyć C4668 jako ostrzeżenie poziomu 1.
Will
56

Oboje są ohydni. Zamiast tego zrób to:

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

Następnie, gdy potrzebujesz kodu debugowania, umieść go w środku D();. A twój program nie jest zanieczyszczony ohydnymi labiryntami #ifdef.

R .. GitHub PRZESTAŃ POMÓC LODOWI
źródło
6
@MatthieuM. Właściwie myślę, że oryginalna wersja była w porządku. Średnik byłby interpretowany jako pusta instrukcja. Jednak zapomnienie średnika może uczynić to niebezpiecznym.
Casey Kuball,
31

#ifdef po prostu sprawdza, czy dany token jest zdefiniowany

#define FOO 0

następnie

#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
Terence Simpson
źródło
20

Mieliśmy ten sam problem w wielu plikach i zawsze występuje problem polegający na tym, że ludzie zapominają o dołączeniu pliku z flagą funkcji (przy podstawie kodu> 41 000 plików jest to łatwe).

Jeśli miałeś feature.h:

#ifndef FEATURE_H
#define FEATURE_H

// turn on cool new feature
#define COOL_FEATURE 1

#endif // FEATURE_H

Ale potem zapomniałeś dołączyć plik nagłówkowy do file.cpp:

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

Wtedy masz problem, kompilator interpretuje COOL_FEATURE jako niezdefiniowany w tym przypadku jako „fałsz” i nie dołącza kodu. Tak, gcc obsługuje flagę, która powoduje błąd dla niezdefiniowanych makr ... ale większość kodu firm trzecich albo definiuje, albo nie definiuje funkcji, więc nie byłoby to tak przenośne.

Przyjęliśmy przenośny sposób korygowania tego przypadku, a także testowania stanu funkcji: makra funkcji.

jeśli zmieniłeś powyższą funkcję. h na:

#ifndef FEATURE_H
#define FEATURE_H

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

#endif // FEATURE_H

Ale potem znowu zapomniałeś dołączyć plik nagłówkowy do file.cpp:

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

Preprocesor popełniłby błąd z powodu użycia niezdefiniowanego makra funkcji.

Brent Priddy
źródło
17

Na potrzeby kompilacji warunkowej #if i #ifdef są prawie takie same, ale nie do końca. Jeśli Twoja kompilacja warunkowa zależy od dwóch symboli, to #ifdef nie będzie działać tak dobrze. Na przykład, załóżmy, że masz dwa symbole kompilacji warunkowej, PRO_VERSION i TRIAL_VERSION, możesz mieć coś takiego:

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

Użycie #ifdef powyższego staje się znacznie bardziej skomplikowane, zwłaszcza uruchomienie części #else.

Pracuję nad kodem, który intensywnie używa kompilacji warunkowej i mamy mieszankę #if & #ifdef. Zwykle używamy # ifdef / # ifndef dla prostego przypadku i #if, gdy obliczane są dwa lub więcej symboli.

Mike Thompson
źródło
1
w #if definedto, co definedjest to słowo klucz lub?
nmxprime
15

Myślę, że to całkowicie kwestia stylu. Żadna z nich nie ma tak naprawdę oczywistej przewagi nad drugą.

Konsekwencja jest ważniejsza niż którykolwiek konkretny wybór, więc radziłbym zebrać się ze swoim zespołem, wybrać jeden styl i trzymać się go.

Derek Park
źródło
8

Ja wolę:

#if defined(DEBUG_ENABLED)

Ponieważ znacznie łatwiej jest stworzyć kod, który szuka odwrotnego warunku:

#if !defined(DEBUG_ENABLED)

vs.

#ifndef(DEBUG_ENABLED)
Jim Buck
źródło
8
Osobiście uważam, że łatwiej przegapić ten mały wykrzyknik!
Jon Cage,
6
Z podświetlaniem składni? :) W podświetlaniu składni, "n" w "ifndef" jest dużo trudniejsze do wykrycia, ponieważ ma ten sam kolor.
Jim Buck
Okej, miałem na myśli, że #ifndef jest łatwiejsze do wykrycia niż #if! Zdefiniowane, gdy porównujesz z #if zdefiniowanym .. ale biorąc pod uwagę wszystkie #if zdefiniowane / # if! Zdefiniowane vs # ifdef / # ifndef, oba są równie nieczytelne!
Jon Cage,
@JonCage Wiem, że minęło kilka lat od tego komentarza, ale chciałbym zaznaczyć, że możesz go napisać, #if ! definedaby uczynić !bardziej widocznym i trudnym do przeoczenia.
Pharap
@Pharap - To z pewnością wygląda na poprawę :)
Jon Cage
7

To kwestia stylu. Ale polecam bardziej zwięzły sposób zrobienia tego:

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

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

Robisz to raz, a następnie zawsze używaj debug_print () do drukowania lub nic nie robisz. (Tak, skompiluje się w obu przypadkach). W ten sposób Twój kod nie zostanie zniekształcony dyrektywami preprocesora.

Jeśli pojawi się ostrzeżenie „wyrażenie nie działa” i chcesz się go pozbyć, oto alternatywa:

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

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

debug_print("i=%d\n", i);
Lew
źródło
3
Być może makro drukowania nie było najlepszym przykładem - tak naprawdę robimy to już w naszej bazie kodu dla naszego bardziej standardowego kodu debugowania. Używamy bitów #if / #ifdefined dla obszarów, w których możesz chcieć włączyć dodatkowe debugowanie.
Jon Cage
5

#ifdaje możliwość ustawienia go na 0, aby wyłączyć tę funkcję, jednocześnie wykrywając, że przełącznik tam jest.
Osobiście zawsze, #define DEBUG 1więc mogę to złapać za pomocą #if lub #ifdef

Martin Beckett
źródło
1
To się nie powiedzie, ponieważ #define DEBUG = 0 nie będzie teraz działać #if, ale będzie działać #ifdef
tloach
1
O to chodzi, mogę całkowicie usunąć DEBUG lub po prostu ustawić go na 0, aby go wyłączyć.
Martin Beckett
powinno być #define DEBUG 1. Nie#define DEBUG=1
Keshava GN
4

#if i # zdefiniuj MY_MACRO (0)

Użycie #if oznacza, że ​​utworzyłeś makro „zdefiniuj”, czyli coś, co będzie przeszukiwane w kodzie i zostanie zastąpione przez „(0)”. To jest „piekło makr”, którego nienawidzę widzieć w C ++, ponieważ zanieczyszcza kod potencjalnymi modyfikacjami.

Na przykład:

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

daje następujący błąd na g ++:

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

Tylko jeden błąd.

Oznacza to, że Twoje makro pomyślnie współdziałało z Twoim kodem C ++: wywołanie funkcji powiodło się. W tym prostym przypadku jest to zabawne. Ale moje własne doświadczenie z makrami grającymi po cichu moim kodem nie jest pełne radości i spełnienia, więc ...

#ifdef i #define MY_MACRO

Używanie #ifdef oznacza, że ​​coś „definiujesz”. Nie znaczy to, że nadajesz mu wartość. Nadal zanieczyszcza, ale przynajmniej zostanie „niczym zastąpiony” i nie będzie postrzegany przez kod C ++ jako instrukcja kodu lagitimate. Ten sam kod powyżej, z prostą definicją, to:

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

Daje następujące ostrzeżenia:

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

Więc...

Wniosek

Wolałbym żyć bez makr w swoim kodzie, ale z wielu powodów (definiowanie zabezpieczeń nagłówka lub debugowanie makr) nie mogę.

Ale przynajmniej chcę, aby były one jak najmniej interaktywne z moim legalnym kodem C ++. Oznacza to używanie #define bez wartości, używanie #ifdef i #ifndef (lub nawet #if zdefiniowanego zgodnie z sugestią Jima Bucka), a przede wszystkim nadawanie im nazw tak długich i tak obcych, których nikt o zdrowych zmysłach nie użyje jest to przypadek i w żaden sposób nie wpłynie to na legalny kod C ++.

Post Scriptum

Teraz, gdy ponownie czytam mój post, zastanawiam się, czy nie powinienem próbować znaleźć jakiejś wartości, która nigdy nie będzie poprawna C ++ do dodania do mojego zdefiniowania. Coś jak

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

który może być użyty z #ifdef i #ifndef, ale nie pozwól na kompilację kodu, jeśli jest używany wewnątrz funkcji ... Próbowałem tego pomyślnie na g ++ i dało to błąd:

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

Ciekawy. :-)

paercebal
źródło
Zgadzam się, że makra mogą być niebezpieczne, ale ten pierwszy przykład byłby dość oczywisty do debugowania i oczywiście daje tylko jeden błąd. Dlaczego miałbyś oczekiwać więcej? Widziałem dużo bardziej paskudne błędy w wyniku makr ...
Jon Cage,
To prawda, że ​​różnica między jednym rozwiązaniem a drugim jest prawie trywialna. Ale w tym przypadku, skoro mówimy o dwóch konkurujących ze sobą stylach kodowania, to nawet trywialnego nie da się zignorować, ponieważ po tym pozostaje tylko osobisty gust (i uważam, że w tym momencie nie powinno się go znormalizować )
paercebal
4

To wcale nie jest kwestia stylu. Również pytanie jest niestety błędne. Nie można porównywać tych dyrektyw preprocesora w sensie lepszego lub bezpieczniejszego.

#ifdef macro

oznacza „jeśli zdefiniowano makro” lub „jeśli makro istnieje”. Wartość makra nie ma tutaj znaczenia. To może być cokolwiek.

#if macro

jeśli zawsze porównuje się z wartością. W powyższym przykładzie jest to standardowe niejawne porównanie:

#if macro !=0

przykład użycia #if

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

teraz możesz umieścić w kodzie definicję CFLAG_EDITION

#define CFLAG_EDITION 1 

lub możesz ustawić makro jako flagę kompilatora. Zobacz także tutaj .

tmanthey
źródło
2

Pierwsza wydaje mi się jaśniejsza. Wydaje się bardziej naturalne, że jest to flaga w porównaniu do zdefiniowanej / nie zdefiniowanej.

axblount
źródło
2

Oba są dokładnie równoważne. W użyciu idiomatycznym #ifdef jest używany tylko do sprawdzenia zdefiniowania (i tego, czego użyłbym w twoim przykładzie), podczas gdy #if jest używany w bardziej złożonych wyrażeniach, takich jak #if zdefiniowane (A) &&! Zdefiniowane (B).

zvrba
źródło
1
OP nie pytał o to, co jest lepsze między „#ifdef” a „#if zdefiniowane”, ale raczej między „# ifdef / # jeśli zdefiniowano” i „#if”.
cholewka
1

Trochę OT, ale włączanie / wyłączanie logowania za pomocą preprocesora jest zdecydowanie nieoptymalne w C ++. Istnieją fajne narzędzia do logowania, takie jak log4cxx Apache, które są open-source i nie ograniczają sposobu dystrybucji aplikacji. Pozwalają również na zmianę poziomów rejestrowania bez ponownej kompilacji, mają bardzo niskie narzuty, jeśli wyłączysz wylogowywanie, i dają możliwość całkowitego wyłączenia wylogowywania w środowisku produkcyjnym.

David Nehme
źródło
1
Zgadzam się i faktycznie robimy to w naszym kodzie, chciałem tylko przykładu czegoś, do czego możesz użyć #if itp.
Jon Cage
1

Kiedyś używałem #ifdef, ale kiedy przełączałem się na Doxygen w celu uzyskania dokumentacji, stwierdziłem, że zakomentowanych makr nie można udokumentować (lub przynajmniej Doxygen generuje ostrzeżenie). Oznacza to, że nie mogę udokumentować makr przełączania funkcji, które nie są obecnie włączone.

Chociaż możliwe jest zdefiniowanie makr tylko dla Doxygen, oznacza to, że zostaną również udokumentowane makra w nieaktywnych częściach kodu. Osobiście chcę pokazać przełączniki funkcji, a poza tym dokumentować tylko to, co jest aktualnie wybrane. Co więcej, sprawia, że ​​kod jest dość niechlujny, jeśli istnieje wiele makr, które muszą być zdefiniowane tylko wtedy, gdy Doxygen przetwarza plik.

Dlatego w tym przypadku lepiej jest zawsze definiować makra i używać #if.

StefanB
źródło
0

Zawsze używałem #ifdef i flag kompilatora, aby to zdefiniować ...

tloach
źródło
Jakiś szczególny powód (z ciekawości)?
Jon Cage,
2
Szczerze mówiąc, nigdy o tym nie myślałem - po prostu jak to zostało zrobione w miejscach, w których pracowałem. Daje to tę zaletę, że zamiast dokonywać zmian w kodzie dla kompilacji produkcyjnej, wszystko, co musisz zrobić, to
``
0

Alternatywnie można zadeklarować stałą globalną i użyć języka C ++ if zamiast preprocesora #if. Kompilator powinien zoptymalizować dla Ciebie nieużywane gałęzie, a Twój kod będzie czystszy.

Oto, co C ++ Gotchas autorstwa Stephena C. Dewhursta mówi o używaniu # if's.

Dima
źródło
1
To kiepskie rozwiązanie, ma następujące problemy: 1. Działa tylko w funkcjach, nie można usunąć niepotrzebnych zmiennych klas itp. 2. Kompilatory mogą generować ostrzeżenia o nieosiągalnym kodzie 3. Kod w if nadal musi zostać skompilowany, co oznacza musisz mieć zdefiniowane wszystkie funkcje debugowania itp.
Don Neufeld,
Najpierw pytanie dotyczyło debugowania printfs, więc niepotrzebne zmienne klas nie stanowią tutaj problemu. Po drugie, biorąc pod uwagę możliwości nowoczesnych kompilatorów, powinieneś używać #ifdefs tak mało, jak to tylko możliwe. W większości przypadków można zamiast tego użyć konfiguracji kompilacji lub specjalizacji szablonów.
Dima,
0

Istnieje różnica w przypadku innego sposobu określenia warunkowego zdefiniowania kierowcy:

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

wynik:

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

Oznacza to, że -DAjest to synonim -DA=1wartości, a jeśli wartość zostanie pominięta, może to prowadzić do problemów w #if Aużytkowaniu.

Tomilov Anatoliy
źródło
0

Lubię, #define DEBUG_ENABLED (0)gdy potrzebujesz wielu poziomów debugowania. Na przykład:

#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

Ułatwia debugowanie wycieków pamięci bez konieczności używania tych wszystkich wierszy dziennika na drodze do debugowania innych rzeczy.

Również #ifndefotoczenie definicji ułatwia wybranie określonego poziomu debugowania w wierszu poleceń:

make -DDEBUG_LEVEL=2
cmake -DDEBUG_LEVEL=2
etc

Gdyby nie to, dałbym przewagę, #ifdefponieważ flaga kompilatora / tworzenia byłaby nadpisana przez tę w pliku. Nie musisz się więc martwić o zmianę nagłówka z powrotem przed zatwierdzeniem.

memtha
źródło