Dlaczego #define TRUE (1 == 1) w makrze boolowskim C zamiast po prostu jako 1?

160

Widziałem definicje w C

#define TRUE (1==1)
#define FALSE (!TRUE)

Czy to konieczne? Jaka jest korzyść z definiowania wartości TRUE jako 1 i FALSE jako 0?

Robert Harvey
źródło
35
I więcej #define TRUE (’/’/’/’):; #define FALSE (’-’-’-’)(zaczerpnięte z coding-guidelines.com/cbook/cbook1_1.pdf strona 871)
osgx,
2
Nie, to paranoja ze strony nieświadomych ^ Wunder, naprawdę. W C 1 i 0 robią we wszystkich okolicznościach to samo.
Jens
@osgx Co to znaczy?
mrgloom

Odpowiedzi:

155

To podejście użyje rzeczywistego booleantypu (i rozwiąże to truei false), jeśli kompilator go obsługuje. (w szczególności C ++)

Jednak lepiej byłoby sprawdzić, czy C ++ jest w użyciu (za pomocą __cplusplusmakra) i czy faktycznie używa truei false.

W kompilatorze C jest to równoważne z 0i 1.
(pamiętaj, że usunięcie nawiasów przerwie to ze względu na kolejność operacji)

SLaks
źródło
7
To nieprawda, boole nie są tutaj używane. Wynik 1==1jest int. (patrz stackoverflow.com/questions/7687403/… .)
Mat.
4
@Mat: Nawet w C ++, z booleantypem?
SLaks
9
Pytanie jest oznaczone jako C, ale faktycznie w C ++ operatory relacyjne zwracają truelub false.
Mat
5
@Mat: Zgaduję, że taki kod jest napisany w nagłówkach C, aby dobrze grać z C ++
SLaks
20
@SLaks Gdyby chciał grać dobrze z C ++, to #define TRUE truei #define FALSE falsekiedykolwiek byłby __cpluspluszdefiniowany.
Nikos C.
137

Odpowiedzią jest przenośność. Wartości liczbowe TRUEi FALSEnie są ważne. Co jest ważne to, że oświadczenie jak if (1 < 2)ma wartość if (TRUE)oraz oświadczeniem jak if (1 > 2)ma wartość if (FALSE).

To prawda, w C (1 < 2)oblicza 1i (1 > 2)oblicza do 0, więc jak powiedzieli inni, nie ma praktycznej różnicy, jeśli chodzi o kompilator. Ale pozwalając kompilatorowi definiować TRUEi FALSEzgodnie z jego własnymi regułami, ujawniasz ich znaczenie programistom i gwarantujesz spójność w swoim programie i każdej innej bibliotece (zakładając, że inna biblioteka jest zgodna ze standardami C ... być zdumionym).


Trochę historii
Niektóre BASIC-y zdefiniowane FALSEjako 0i TRUEjako -1. Podobnie jak wiele współczesnych języków, interpretowali każdą wartość niezerową jako TRUE, ale oceniali wyrażenia boolowskie, które były prawdziwe jako -1. Ich NOTdziałanie zostało zrealizowane poprzez dodanie 1 i odwrócenie znaku, ponieważ było to wydajne. Stało się więc „NIE x” -(x+1). Efektem ubocznym jest to, że wartość taka jak 5szacuje się do TRUE, ale NOT 5szacuje się do -6, co też jest TRUE! Znalezienie tego rodzaju błędu nie jest zabawne.

Najlepsze praktyki
Biorąc pod uwagę faktyczne zasady, że zero jest interpretowane jako, FALSEa każda wartość niezerowa jest interpretowana jako TRUE, nigdy nieTRUEFALSE należy porównywać wyrażeń wyglądających logicznie z lub . Przykłady:

if (thisValue == FALSE)  // Don't do this!
if (thatValue == TRUE)   // Or this!
if (otherValue != TRUE)  // Whatever you do, don't do this!

Czemu? Ponieważ wielu programistów używa skrótu do traktowania ints jako bools. Nie są takie same, ale kompilatory generalnie na to pozwalają. Na przykład pisanie jest całkowicie legalne

if (strcmp(yourString, myString) == TRUE)  // Wrong!!!

Że wygląda uzasadnione, a kompilator będzie szczęśliwie zaakceptować, ale to chyba nie robi tego, co tylko chcesz. Dzieje się tak, ponieważ zwracana wartość strcmp()to

      0 jeśli yourString == myString
    <0 jeśli yourString < myString
    > 0 jeśliyourString > myString

Zatem powyższa linia zwraca TRUEtylko wtedy, gdy yourString > myString.

Właściwy sposób to zrobić

// Valid, but still treats int as bool.
if (strcmp(yourString, myString))

lub

// Better: lingustically clear, compiler will optimize.
if (strcmp(yourString, myString) != 0)

Podobnie:

if (someBoolValue == FALSE)     // Redundant.
if (!someBoolValue)             // Better.
return (x > 0) ? TRUE : FALSE;  // You're fired.
return (x > 0);                 // Simpler, clearer, correct.
if (ptr == NULL)                // Perfect: compares pointers.
if (!ptr)                       // Sleazy, but short and valid.
if (ptr == FALSE)               // Whatisthisidonteven.

Często znajdziesz niektóre z tych „złych przykładów” w kodzie produkcyjnym i wielu doświadczonych programistów przysięga na nie: działają, niektórzy są krótsi niż ich (pedantycznie?) Poprawne alternatywy, a idiomy są prawie powszechnie rozpoznawane. Ale zastanów się: „właściwe” wersje nie są mniej wydajne, są przenośne, przejdą nawet najbardziej rygorystyczne lintery i nawet nowi programiści je zrozumieją.

Czy to nie jest tego warte?

Adam Liss
źródło
6
(1==1)nie jest bardziej przenośny niż 1. Własne reguły kompilatora są regułami języka C, który jest jasny i jednoznaczny w zakresie semantyki operatorów równości i relacyjnych. Nigdy nie widziałem, żeby kompilator źle to robił.
Keith Thompson
1
W rzeczywistości strcmpwiadomo, że wartość zwracana przez jest mniejsza niż, równa lub większa niż 0. Nie ma gwarancji, że będzie to -1, 0 lub 1, a istnieją platformy, które nie zwracają tych wartości, aby przyspieszyć implementację. Więc jeśli strcmp(a, b) == TRUEwtedy, a > bale odwrotna implikacja może nie mieć miejsca.
Maciej Piechotka
2
@KeithThompson - Być może „przenośność” to zły termin. Ale faktem jest, że (1 == 1) jest wartością logiczną; 1 nie jest.
Adam Liss,
2
@AdamLiss: W języku C (1==1)i 1oba są stałymi wyrażeniami typu into wartości 1. Są semantycznie identyczne. Przypuszczam, że możesz napisać kod przeznaczony dla czytelników, którzy tego nie wiedzą, ale gdzie to się kończy?
Keith Thompson,
2
„nie” 5 jest rzeczywiście -6 na poziomie bitowym.
woliveirajr
51

Ta (1 == 1)sztuczka jest przydatna do definiowania TRUEw sposób przezroczysty dla C, ale zapewnia lepsze pisanie w C ++. Ten sam kod może być zinterpretowany jako C lub C ++, jeśli piszesz w dialekcie zwanym „Clean C” (który kompiluje się jako C lub C ++) lub jeśli piszesz pliki nagłówkowe API, które mogą być używane przez programistów C lub C ++.

W jednostkach tłumaczeniowych C 1 == 1ma dokładnie to samo znaczenie, co 1; i 1 == 0ma takie samo znaczenie jak 0. Jednak w jednostkach tłumaczeniowych C ++ 1 == 1ma typ bool. Zatem TRUEzdefiniowane w ten sposób makro lepiej integruje się z C ++.

Przykładem tego, jak lepiej integruje się, jest to, że na przykład jeśli funkcja fooma przeciążenia dla inti dla bool, to foo(TRUE)wybierze boolprzeciążenie. Jeśli TRUEjest zdefiniowane jako 1, to nie będzie dobrze działać w C ++. foo(TRUE)będzie chciał intprzeciążenia.

Oczywiście C99 wprowadził bool, truei falsea te mogą być używane w plikach nagłówkowych, że praca z C99 i C.

Jednak:

  • praktyka ta definiowania TRUEi FALSEjak (0==0)i (1==0)wyprzedza C99.
  • nadal istnieją dobre powody, aby trzymać się z dala od C99 i pracować z C90.

Jeśli pracujesz w mieszanym projekcie C i C ++ i nie chcesz C99, zdefiniuj małe litery true, falsea boolzamiast tego.

#ifndef __cplusplus
typedef int bool;
#define true (0==0)
#define false (!true)
#endif

To powiedziawszy, 0==0sztuczka była (jest?) Używana przez niektórych programistów nawet w kodzie, który nigdy nie był przeznaczony do współpracy z C ++ w jakikolwiek sposób. To nic nie kupuje i sugeruje, że programista ma niezrozumienie, jak wartości logiczne działają w C.


Na wypadek, gdyby wyjaśnienie C ++ nie było jasne, oto program testowy:

#include <cstdio>

void foo(bool x)
{
   std::puts("bool");  
}

void foo(int x)
{
   std::puts("int");  
}

int main()
{
   foo(1 == 1);
   foo(1);
   return 0;
}

Wyjście:

bool
int

Co do pytania z komentarzy, w jaki sposób przeciążone są funkcje C ++ związane z programowaniem mieszanym C i C ++. To tylko ilustruje różnicę typów. Ważnym powodem, dla którego chcemy, trueaby stała jest boolkompilowana jako C ++, jest czysta diagnostyka. Przy najwyższych poziomach ostrzeżenia kompilator C ++ może ostrzec nas o konwersji, jeśli jako boolparametr przekażemy liczbę całkowitą . Jednym z powodów pisania w Clean C jest nie tylko to, że nasz kod jest bardziej przenośny (ponieważ jest to zrozumiałe dla kompilatorów C ++, nie tylko C), ale możemy skorzystać z opinii diagnostycznych kompilatorów C ++.

Kaz
źródło
3
Doskonała i niedoceniona odpowiedź. Nie jest wcale oczywiste, że te dwie definicje TRUEbędą się różnić w C ++.
user4815162342
4
W jaki sposób przeciążone funkcje są istotne dla kodu, który kompiluje się zarówno jako C, jak i C ++?
Keith Thompson
@KeithThompson Nie chodzi tylko o przeładowanie, ale ogólnie o poprawne pisanie. Przeciążanie jest po prostu najbardziej praktycznym przykładem, jeśli chodzi o grę. Oczywiście kod C ++ bez przeciążeń, szablonów i wszystkich tych „skomplikowanych” rzeczy usuniętych w celu „zgodności z C” w ogóle nie przejmuje się typami, ale to nie znaczy, że należy obalić koncepcyjne ograniczenia typu w danym języku .
Christian Rau,
1
@ChristianRau: Co masz na myśli, mówiąc, że „nie przejmuje się typami”? Typy są kluczowe dla języka C; każde wyrażenie, wartość i obiekt w programie C ma dobrze zdefiniowany typ. Jeśli chcesz zdefiniować coś inaczej w C i C ++ (w rzadkich przypadkach, gdy faktycznie musisz napisać kod, który kompiluje się zarówno jako C, jak i C ++), możesz użyć go #ifdef __cplusplusdo wyraźniejszego wyrażenia swoich zamiarów.
Keith Thompson,
@KeithThompson Tak, wiem, jak ważne są typy. Chodzi o to, że bez wszystkich rzeczy uwzględniających typ, takich jak przeciążanie i szablony, rzeczy takie jak rozróżnienie między booli intnie mają większego znaczenia w praktyce, ponieważ są one niejawnie konwertowane na siebie nawzajem (aw C faktycznie „to samo” , zwróć uwagę na cudzysłowy , choć) i nie ma wielu sytuacji, w których naprawdę trzeba by rozprawić się między nimi. „Niewiele” było prawdopodobnie zbyt ciężkie, „znacznie mniej w porównaniu z kodem używającym szablonów i przeciążeniem” mogłoby być lepsze.
Christian Rau
18
#define TRUE (1==1)
#define FALSE (!TRUE)

jest równa

#define TRUE  1
#define FALSE 0

w C.

Wynikiem operatorów relacyjnych jest 0lub 1. 1==1ma gwarancję oceny 1i !(1==1)ma gwarancję oceny 0.

Nie ma absolutnie żadnego powodu, aby używać pierwszego formularza. Należy zauważyć, że pierwsza postać nie jest jednak mniej wydajna, ponieważ w prawie wszystkich kompilatorach wyrażenie stałe jest obliczane w czasie kompilacji, a nie w czasie wykonywania. Jest to dozwolone zgodnie z tą zasadą:

(C99, 6.6p2) „Wyrażenie stałe może być oceniane podczas tłumaczenia, a nie w czasie wykonywania, i odpowiednio może być używane w dowolnym miejscu, w którym może być stała”.

PC-Lint wyda nawet komunikat (506, stała wartość boolean), jeśli nie użyjesz literału for TRUEi FALSEmakr:

Dla C TRUEnależy zdefiniować jako 1. Jednak inne języki używają wartości innych niż 1, więc niektórzy programiści uważają, że !0jest to bezpieczne.

Również w C99 stdbool.hdefinicje makr boolowskich truei false bezpośrednio używają literałów:

#define true   1
#define false  0
ouah
źródło
1
Mam wątpliwości, że TRUE jest zastępowane przy każdym użyciu 1 == 1, podczas gdy samo użycie 1 zastąpi 1, czy pierwsza metoda nie jest dodatkowym obciążeniem porównawczym ... czy jest to zoptymalizowany kompilator?
pinkpanther
4
Wyrażenia stałe @pinkpanther są zwykle oceniane w czasie kompilacji, więc nie powodują żadnych narzutów.
ouah
2
1==1jest gwarantowany do oceny1
ouah,
3
@NikosC. to dobre pytanie. Ma to znaczenie dla kodu formularza if(foo == true), który zmieni się od zwykłej złej praktyki do pełnego błędu.
djechlin
1
+1 za wskazanie niebezpieczeństw (x == TRUE)może mieć inną wartość niż x.
Joshua Taylor
12

Oprócz C ++ (już wspomnianego), kolejną korzyścią są narzędzia do analizy statycznej. Kompilator usunie wszelkie nieefektywności, ale analizator statyczny może używać własnych typów abstrakcyjnych do rozróżniania wyników porównania od innych typów całkowitych, dzięki czemu wie niejawnie, że PRAWDA musi być wynikiem porównania i nie należy zakładać, że jest zgodny z liczbą całkowitą.

Oczywiście C mówi, że są kompatybilne, ale możesz zabronić celowego korzystania z tej funkcji, aby pomóc w wyróżnieniu błędów - na przykład, gdy ktoś mógł pomylić &i &&lub spartaczył pierwszeństwo swojego operatora.

sh1
źródło
1
To dobra uwaga i być może niektóre z tych narzędzi potrafią nawet wyłapać głupi kod if (boolean_var == TRUE) poprzez rozwinięcie, do if (boolean_var == (1 == 1))którego dzięki rozszerzonej informacji o typie (1 == 1)węzła wpada do wzorca if (<*> == <boolean_expr>).
Kaz
4

Praktyczna różnica to żadna. 0jest oceniany falsei 1jest oceniany do true. Fakt, że używasz wyrażenia logicznego ( 1 == 1) lub 1, aby zdefiniować true, nie robi żadnej różnicy. Obaj są oceniani int.

Zauważ, że biblioteka standardowa C zapewnia nagłówek specyficzny dla zdefiniowania wartości logicznych: stdbool.h.

But
źródło
oczywiście, że nie ... ale niektórzy mogą pomyśleć inaczej, szczególnie w przypadku liczb ujemnych, dlatego :)
pinkpanther
Co? Masz to od tyłu. truejest oceniany 1i falsejest oceniany do 0. C nie wie o rodzimych typach boolowskich, są po prostu intami.
djechlin
W języku C operatory relacyjne i równości dają wyniki typu int, z wartością 0lub 1. C ma rzeczywisty typ boolowski ( _Boolz makrem boolzdefiniowanym w <stdbool.h>, ale zostało dodane tylko w C99, co nie zmieniło semantyki operatorów, aby używały nowego typu.
Keith Thompson
@djechlin: Zgodnie z normą 1999, C nie posiada natywną typu Boolean. Nazywa się _Booli <stdbool.h>ma #define bool _Bool.
Keith Thompson
@KeithThompson, masz rację co do 1 == 1oceny jako int. Edytowano.
But
3

Nie znamy dokładnej wartości równej TRUE, a kompilatory mogą mieć własne definicje. Więc to, co zamierzasz, to użyć wewnętrznego kompilatora do definicji. Nie zawsze jest to konieczne, jeśli masz dobre nawyki programistyczne, ale możesz uniknąć problemów związanych z pewnym złym stylem kodowania, na przykład:

if ((a> b) == TRUE)

Może to być katastrofą, jeśli ręcznie zdefiniujesz PRAWDA jako 1, podczas gdy wewnętrzna wartość PRAWDA będzie inna.

capiggue
źródło
W języku C >operator zawsze daje 1 dla prawdy, 0 dla fałszu. Nie ma możliwości, aby jakikolwiek kompilator C pomylił się. Porównania równości do TRUEi FALSEsą w złym stylu; powyższe jest wyraźniej zapisane jako if (a > b). Ale pomysł, że różne kompilatory C mogą inaczej traktować prawdę i fałsz, jest po prostu błędny.
Keith Thompson,
2
  1. Element listy

Zwykle w języku programowania C 1 jest definiowane jako prawda, a 0 jako fałsz. Dlatego dość często widzisz:

#define TRUE 1 
#define FALSE 0

Jednak każda liczba różna od 0 zostanie uznana za prawdziwą również w instrukcji warunkowej. Dlatego korzystając z poniższych:

#define TRUE (1==1)
#define FALSE (!TRUE)

Możesz po prostu wyraźnie pokazać, że starasz się grać bezpiecznie, czyniąc fałsz równym temu, co nie jest prawdą.

Sabashan Ragavan
źródło
4
Nie nazwałbym tego „grając bezpiecznie” - raczej dajesz sobie fałszywe poczucie bezpieczeństwa.
dodgethesteamroller