Jaki jest cel bloku „if (0)” w bloku if-else?

141

Moje pytanie dotyczy linii, o której wspomniałem w temacie i którą widzę w wielu miejscach w kodzie produkcyjnym.

Ogólny kod wygląda następująco:

if (0) {
    // Empty braces
} else if (some_fn_call()) {
    // actual code
} else if (some_other_fn_call()) {
    // another actual code
    ...
} else {
    // default case
}

Inne gałęzie są nieistotne dla mojego pytania. Zastanawiam się, jakie jest znaczenie wstawiania if (0)tutaj. Nawiasy są puste, więc nie sądzę, że ma komentować jakiś blok kodu. Czy zmusza kompilator do optymalizacji, czy też ma inne intencje?

Próbowałem poszukać tego wyraźnego przypadku tutaj w SO iw Internecie, ale bezskutecznie. Są podobne pytania dotyczące JavaScript, ale nie C. Jest jeszcze jedno pytanie: Co się stanie, gdy zero zostanie przypisane w warunku „if”? , ale omawia przypisanie zerowe do zmiennej, a nie samo użycie „if (0)”.

Zzaponka
źródło
2
To stwierdzenie wydaje się nieistotne. Wygeneruj kod asemblera z tą instrukcją i bez niej, a zobaczysz, co się dzieje pod maską.
haccks
2
Możliwe, że jest to kod generowany automatycznie.
dziwaczny

Odpowiedzi:

91

Czasami używam tego do symetrii, więc mogę else if{swobodnie poruszać drugim edytorem, nie myśląc o pierwszym if.

Semantycznie

if (0) {
    // Empty braces
} else 

część nic nie robi i możesz liczyć na optymalizatory, które ją usuną.

PSkocik
źródło
239
Osobista opinia: Chociaż może to być kod powodu, dla którego jest napisany tak, jak jest, myślę, że jest to złe uzasadnienie. Kod jest czytany częściej niż napisany, a ten niepotrzebny kod po prostu zwiększa narzut przetwarzania dla czytelnika.
user694733
13
@ user694733: Można argumentować, że wspólny if elseprefiks do wszystkich znaczących ścieżek kodu ładnie dopełnia warunki i ułatwia ich skanowanie. (Jest to jednak subiektywne i zależałoby w dużej mierze od tego, co naprawdę znajduje się w warunkach i blokach kodu).
M Oehm
72
Nie sądzę, aby if (0) {..}wprowadzał jakikolwiek problem z parsowaniem / czytelnością. Powinno to być oczywiste dla każdego, kto trochę zna C. To nie jest problem. Problemem jest pytanie uzupełniające po przeczytaniu: „Po co to do diabła jest?” O ile nie jest to do celów debugowania / tymczasowych (tj. Intencją jest „włączenie” tego ifbloku później), zalecałbym całkowite usunięcie . Zasadniczo „czytanie” takiego kodu prawdopodobnie spowodowałoby niepotrzebną „pauzę” dla czytelnika bez dobrego powodu. I to wystarczający powód, aby go usunąć.
PP
77
Wydaje się, że zdecydowanie pogarsza to czytelność. Był tak zły, że wysłał programistę do SO z pytaniem, do czego służy. Nie jest to dobry znak.
Vectorjohn
26
Nawet używając tego wzorca nie wiem, czy można „poruszać else ifsię po edytorze bez obaw”, ponieważ warunki mogą się nie wykluczać, w takim przypadku kolejność ma znaczenie. Osobiście użyłbym tylko ifi wykonałbym wczesny powrót , wyodrębniając łańcuch logiczny do oddzielnej funkcji, jeśli to konieczne.
John Wu
105

Może to być przydatne, jeśli istnieją #ifoświadczenia, ala

   if (0)
   {
       // Empty block
   }
#if TEST1_ENABLED
   else if (test1())
   {
      action1();
   }
#endif
#if TEST2_ENABLED
   else if (test2())
   {
      action2();
   }
#endif

itp.

W takim przypadku każdy (i wszystkie) z testów można #ifusunąć, a kod zostanie poprawnie skompilowany. Prawie wszystkie kompilatory usuną tę if (0) {}część. Prosty autogenerator mógłby wygenerować taki kod, ponieważ jest nieco łatwiejszy do zakodowania - nie musi oddzielnie uwzględniać pierwszego włączonego bloku.

CSM
źródło
5
W wielu przypadkach łańcuch if/ else ifnie jest używany tak często jako drzewo decyzyjne, ale raczej jako konstrukcja „działaj po pierwszym dopasowaniu warunku”, gdzie warunek mający najwyższy priorytet nie jest szczególnie „specjalny”. Chociaż nie widziałem tego if(0)jako sposobu, aby wszystkie prawdziwe gałęzie miały spójną składnię, podoba mi się spójna składnia, którą ułatwia.
supercat
1
W tym przypadku nie jest to nawet przydatne, ponieważ możesz osiągnąć ten sam efekt bez: po prostu podziel else iflinię na dwie i umieść między nimi ochronę preprocesora.
Konrad Rudolph
1
@KonradRudolph Nie śledzę; jak byś to napisał?
JiK
1
@JiK Usunąłbym if (0)gałąź i sformatowałbym resztę tak, aby elsebyła na własnej linii, otoczona strażnikiem wzdłuż linii #if TEST1_ENABLED && TEST2_ENABLED.
Konrad Rudolph
5
@KonradRudolph to w porządku, jeśli chcesz podwoić liczbę strażników i potroić liczbę wymienionych warunków ochrony, jak sądzę.
hobbs
44

Widziałem podobny wzorzec używany w wygenerowanym kodzie. Na przykład w SQL widziałem biblioteki emitujące następującą whereklauzulę.

where 1 = 1

Prawdopodobnie ułatwia to po prostu dodanie innych kryteriów, ponieważ wszystkie dodatkowe kryteria można poprzedzić andzamiast dodatkowego sprawdzenia, czy jest to pierwsze kryterium, czy nie.

Seth Flowers
źródło
4
1=1Jest „przydatna”, ponieważ zawsze można dodać wherez przodu, bezwarunkowo. W przeciwnym razie musisz sprawdzić, czy jest pusty, a jeśli tak, unikaj generowania whereklauzuli.
Bakuriu
2
Ponadto większość baz danych automatycznie „usunie” element 1=1z WHEREpliku, więc nie ma to wpływu na wydajność.
Załóż pozew Moniki
7
Jest to dopuszczalne w bibliotece, która automatycznie generuje zapytania SQL, których najprawdopodobniej nigdy nie widzi nawet zespół DevOps. Nie jest to „dopuszczalne” w kodzie wysokiego poziomu, który musi być wielokrotnie zapisywany i czytany.
phagio
Jest to bardzo przydatne podejście podczas generowania dynamicznego kodu SQL z nieznaną liczbą warunków końcowych.
Skipper
1
@freakish rzeczywiście napisałem coś przeciwnego: słabo czytelna składnia jest dopuszczalna w generowanym kodzie, ponieważ najprawdopodobniej nigdy nie zostanie odczytany, a nie w kodzie funkcjonalnym wysokiego poziomu, który jest obsługiwany przez programistów.
phagio
44

Jak napisano, if (0) {}klauzula kompiluje się do zera.

Podejrzewam, że funkcja klauzuli na szczycie tej drabiny jest zapewnienie łatwego miejsce tymczasowe wyłączenie wszystkich innych funkcji na raz (dla celów porównawczych debugowania lub) zmieniając 0Do 1lub true.

Russell Borogove
źródło
2
Udało mi się to. Nie widziałem żadnego innego powodu poza debugowaniem.
tfont
16

Nie jestem pewien żadnych optymalizacji, ale moje dwa centy:

Stało się tak z powodu pewnej modyfikacji kodu, w której usunięto jeden podstawowy warunek ( ifpowiedzmy wywołanie funkcji w bloku początkowym ), ale programiści / opiekunowie

więc zamiast usuwać powiązany ifblok, po prostu zmienili warunek na if(0)i ruszyli dalej.

Sourav Ghosh
źródło
3
Czy nie if(0)zmniejsza też zasięgu oddziałów?
David Szalai
1
@DavidSzalai Nie do końca - co najwyżej zmniejszy się o 1 (z poprzednich 2) - ale według mojej najlepszej wiedzy nadal wymagane będzie jedno trafienie do pokrycia.
Sourav Ghosh
15

To gnicie kodu.

W pewnym momencie, że „jeśli” zrobiło coś pożytecznego, sytuacja się zmieniła, być może oceniana zmienna została usunięta.

Osoba, która naprawiała / zmieniała system, w jak najmniejszym stopniu wpływała na logikę systemu, więc po prostu upewniła się, że kod zostanie ponownie skompilowany. Więc zostawia „jeśli (0)”, ponieważ jest to szybkie i łatwe i nie jest do końca pewien, czy tego chce. Sprawia, że ​​system działa i nie wraca, aby go całkowicie naprawić.

Następnie przychodzi następny programista i uważa, że ​​zostało to zrobione celowo i komentuje tylko tę część kodu (ponieważ i tak nie jest ona oceniana), a następnym razem, gdy kod zostanie dotknięty, te komentarze są usuwane.

Ciemna materia
źródło
2
Tak. W przypadku starego kodu wprowadzaj jednorazowo jedną zmianę usuwającą martwy kod. Nie potrafię zliczyć, ile razy przeszedłem szaleństwo przeciwko „martwemu” kodowi tylko po to, by odkryć, że był jakiś dziwny efekt uboczny, którego nie udało się przeciąć i spalić.
Julie w Austin
15

Jedna możliwość, o której jeszcze nie wspomniano: if (0) {linia mogłaby stanowić dogodne miejsce na punkt przerwania.

Debugowanie jest często wykonywane na niezoptymalizowanym kodzie, więc zawsze fałszywy test będzie obecny i będzie mógł mieć ustawiony na nim punkt przerwania. Po skompilowaniu do produkcji linia kodu zostanie zoptymalizowana. Pozornie bezużyteczna linia zapewnia funkcjonalność do programowania i testowania kompilacji bez wpływu na kompilacje wydań.

Powyżej znajdują się również inne dobre sugestie; jedynym sposobem, aby naprawdę wiedzieć, jaki jest cel, jest wyśledzenie autora i zapytanie. Twój system kontroli kodu źródłowego może w tym pomóc. (Poszukaj blamefunkcji typu -type).

studog
źródło
9

Widziałem nieosiągalne bloki kodu we wstępnie rozwiniętym JavaScript, które zostały wygenerowane przy użyciu języka szablonów.

Na przykład kod, który czytasz, mógł zostać wklejony z serwera, który wstępnie oceniał pierwszy warunek, który w tym czasie opierał się na zmiennej dostępnej tylko po stronie serwera.

if ( ${requestIsNotHttps} ){ ... }else if( ...

które kiedyś wstępnie skompilowano:

if ( 0 ){ ... }else if ( ...

Mam nadzieję, że pomoże ci to w relatywizacji potencjalnego niskiego poziomu aktywności klawiatury w erze pro-recyklingowych programistów, dla której przejawiam entuzjazm!

simonarame
źródło
1
Zgadzam się, że w dobie wszechobecnej automatyzacji powinniśmy bardziej polegać na kodzie generowanym automatycznie, ponieważ pozwala nam on spędzać więcej czasu na rzeczywistych rzeczach. Ale na razie dokładnie interesuję się tym, jak to wszystko jest zorganizowane pod maską.
Zzaponka
8

Konstrukcja ta może być również używana w C do implementacji programowania ogólnego z bezpieczeństwem typów, opierając się na fakcie, że nieosiągalny kod jest nadal sprawdzany przez kompilator:

// this is a generic unsafe function, that will call fun(arg) at a later time
void defer(void *fun, void *arg);

// this is a macro that makes it safer, by checking the argument
// matches the function signature
#define DEFER(f, arg) \
   if(0) f(arg); \              // never actually called, but compile-time checked
   else defer(f, (void *)arg);  // do the unsafe call after safety check

void myfunction(int *p);

DEFER(myfunction, 42);     // compile error
int *b;
DEFER(myfunction, b);      // compiles OK
philfr
źródło
6

Myślę, że to po prostu zły kod. Pisząc szybki przykład w Compiler Explorer, widzimy, że zarówno w gcc, jak i clang nie jest generowany żaden kod dla if (0)bloku, nawet przy całkowicie wyłączonych optymalizacjach:

https://godbolt.org/z/PETIks

Zabawa z usuwaniem if (0)przyczyn nie powoduje żadnych zmian w wygenerowanym kodzie, więc dochodzę do wniosku, że nie jest to optymalizacja.

Możliwe, że kiedyś było coś w górnym ifbloku, co zostało później usunięte. Krótko mówiąc, wygląda na to, że usunięcie go spowodowałoby wygenerowanie dokładnie tego samego kodu, więc możesz to zrobić.

cha0site
źródło
6

Jak już powiedziano, zero jest oceniane jako fałszywe, a gałąź prawdopodobnie zostanie zoptymalizowana przez kompilator.

Widziałem to również wcześniej w kodzie, w którym dodano nową funkcję i potrzebny był wyłącznik awaryjny (jeśli coś pójdzie nie tak z funkcją, możesz ją po prostu wyłączyć), a jakiś czas później, gdy wyłącznik awaryjny został usunięty, programista nie usunął też gałęzi, np

if (feature_a_active()) {
    use_feature_a();
} else if (some_fn()) {
   ...

stał się

if (0) {
   // empty
} else if (some_fn()) {
   ...
sergiopm
źródło
1

Pomaga zdebugować ten blok, po prostu wstawiając blok 1. To wyłącza wszystkie funkcje if else block. Możemy również rozszerzyć blok if else.

Abdul Ahad Sheikh
źródło
1
    Actually according to my opinion, if we put any variable for checking inside
    e.g:-
public static void main(string args[])
{
        var status;
        var empList=_unitofWork.EmpRepository.Get(con=>con.isRetired==true);
        //some code logic 
        if(empList.count>0)
        {
          status=true;
        }
        if(status)
        {
         //do something
        }
        else
        {
        //do something else
        }
}
     if then its dynamically get the value in run time and invoke the logic inside it, else its simply extra line of code i guess.

    Anybody have any depth knowledge why this thing is used....or agree with me.
    kindly respond. 
Sagar Kumar Choudhury
źródło
1

@ PSkocik odpowiedź jest w porządku, ale dodaję moje dwa centy. Nie jestem pewien, czy powinienem to zrobić jako komentarz, czy jako odpowiedź; wybierając to drugie, ponieważ IMHO jest warte zobaczenia przez innych, podczas gdy komentarze są często niewidoczne.

Nie tylko czasami używam

if(0) {
   //deliberately left empty
} else if( cond1 ) {
   //deliberately left empty
} else if( cond2 ) {
   //deliberately left empty
...
} else {
   // no conditions matched
}

Ale czasami to robię

if( 1 
    && cond1 
    && cond2
    ...
    && condN
) {

lub

if( 0 
    || cond1 
    || cond2
    ...
    || condN
) {

dla skomplikowanych warunków. Z tych samych powodów - łatwiejsza edycja, #ifdef itp.

Jeśli o to chodzi, w Perlu zrobię

@array = (  
    elem1,
    elem2,
    ...
    elem1,
) {
  • zwróć uwagę na przecinek na końcu listy. Zapomniałem, czy przecinki są separatorami lub ogranicznikami na listach C i C ++. IMHO to jest jedna rzecz, której się nauczyliśmy: [ Czy końcowe przecinki w Perlu to zła praktyka? przecinki] to dobra rzecz. Jak w przypadku każdej nowej notacji, przyzwyczajenie się do niej zajmuje trochę czasu.

Porównuję if(0)kod do seplenienia

(cond   (test1    action1)
   (test2    action2)
   ...
   (testn   actionn))

który, jak się domyślacie, mogę wcisnąć jako

(cond   
   (test1    action1)
   (test2    action2)
   ...
   (testn   actionn)
)

Czasami próbowałem sobie wyobrazić, jak mogłaby wyglądać bardziej czytelna dla człowieka składnia.

Być może

IF
:: cond1 THEN code1
:: cond2 THEN code2
...
:: condN THEN codeN
FI

zainspirowany [ https://en.wikipedia.org/wiki/Guarded_Command_Language#Selection:_if][Guarded Command Language]] od Dikstry .

Ale ta składnia implikuje, że warunki są oceniane równolegle, podczas gdy if...else-ifimplikuje sekwencyjną i priorytetową ocenę warunków.

Zacząłem robić tego typu rzeczy, pisząc programy, które generowały inne programy, gdzie jest to szczególnie wygodne.

Skoro już o tym mowa, pisząc RTL przy użyciu starego iHDL Intela, zakodowałem takie rzeczy jak

   IF 0 THEN /*nothing*/
   **FORC i FROM 1 TO 10 DOC** 
   ELSE IF signal%i% THEN    
      // stuff to do if signal%i% is active
   **ENDC** 
   ELSE   
      // nothing matched 
   ENDIF

gdzie FORC..DOC..ENDCjest konstrukcją pętli preprocesora makra, która rozwija się do

   IF 0 THEN /*nothing*/
   ELSE IF signal1 THEN    
      // stuff to do if signal1 is active
   ELSE IF signal2 THEN    
      // stuff to do if signal2 is active
   ...
   ELSE IF signal100 THEN    
      // stuff to do if signal100 is active
   ELSE   
      // nothing matched 
   ENDIF

To było pojedyncze przypisanie, niewymagający, kod, więc ustawienie zmiennej stanu nie było dozwolone, jeśli trzeba było wykonać takie czynności, jak znalezienie pierwszego ustawionego bitu.

   IF 0 THEN /*nothing*/
   ELSE IF signal1 THEN    
      found := 1
   ELSE IF signal2 THEN    
      found := 2
   ...
   ELSE IF signal100 THEN    
      found := 100
   ELSE   
      // nothing matched 
   ENDIF

Jeśli się nad tym zastanowić, to mogło być pierwsze miejsce, w którym zetknąłem się z takimi konstrukcjami.

Przy okazji, zastrzeżenia, które niektórzy mieli do stylu if (0) - że warunki else-if są sekwencyjnie zależne i nie można ich dowolnie zmieniać - nie dotyczą logiki AND i OR oraz XOR w RTL - ale mają zastosowanie do obwód && i ||.

Krazy Glew
źródło
-1

Widziałem, że jest to używane na przykład do obsługi błędów

if(0){
lable1:
   //do something
}
if(0){
lable2:
   //do something
}
.
.
and so on.

if(condition_fails)
   goto lable1;

Może to być pomocne, gdy do zarządzania błędami używa się goto, instrukcje są wykonywane tylko wtedy, gdy wystąpi błąd. Widziałem to w bardzo starym kodzie C (gdzie argumenty funkcji są zapisywane poza '()'), nie sądzę, żeby ktokolwiek teraz to śledził.

Jayachandra M.
źródło
-2

Widziałem to kilka razy, myślę, że najbardziej prawdopodobnym powodem jest ocena czegoś w starszej / innej wersji / gałęzi kodu lub być może w celu debugowania i zmiana tego na if(0)jest nieco leniwym sposobem usuwania tego, co tam było .

John U
źródło