Dlaczego operator trójskładnikowy jest używany do definiowania 1 i 0 w makrze?

79

Używam zestawu SDK do osadzonego projektu. W tym kodzie źródłowym znalazłem kod, który przynajmniej wydał mi się dziwny. W wielu miejscach w SDK jest kod źródłowy w tym formacie:

#define ATCI_IS_LOWER( alpha_char )  ( ( (alpha_char >= ATCI_char_a) && (alpha_char <= ATCI_char_z) ) ? 1 : 0 )

#define ATCI_IS_UPPER( alpha_char )  ( ( (alpha_char >= ATCI_CHAR_A) && (alpha_char <= ATCI_CHAR_Z) ) ? 1 : 0 )

Czy użycie tutaj operatora trójskładnikowego ma znaczenie?

Nie jest

#define FOO (1 > 0)

taki sam jak

#define BAR ( (1 > 0) ? 1 : 0)

?

Próbowałem to ocenić, używając

printf("%d", FOO == BAR);

i otrzymujemy wynik 1, więc wydaje się, że są równe. Czy istnieje powód, aby pisać kod tak, jak oni?

Viktor S
źródło
8
Nie, nie ma powodu. Masz rację.
Art.
29
Częściowo nie na temat: Kiedy kończy się szaleństwo korzystania z preprocesora? Istnieje tu potencjalna możliwość wielokrotnej oceny funkcji. Po prostu niepotrzebne.
stefan
3
Czasami miło jest też powiedzieć wprost. Operator trójargumentowy tutaj na pierwszy rzut oka wyjaśnia, że ​​celem makra jest zwrócenie wartości logicznej.
fajka
5
Przynajmniej makra powinny używać (alpha_char)zamiast tego alpha_char, aby upewnić się, że nie zepsują się, gdy ktoś spróbuje czegoś szalonegoATCI_IS_LOWER(true || -1) .
Justin Time - Przywróć Monikę
5
Wygląda na to, że CI napisane dawno temu. Chciałbym wrócić do C od Pascala, który miał specjalny booleantyp, więc zmarnowany czas zmienia niewypowiedziane okropności jak if (n)się if (0 != n)zapewne dodanie wątpliwy obsady „aby upewnić się”. Jestem pewien, że ja też zabezpieczałem nierówności if (a < b) .... Pewnie, że wyglądał jak Pascala if a < b then ..., ale wiedziałem, że C na < nie było booleanale int, i intmoże być prawie wszystko ! Strach prowadzi do złocenia, złocenie prowadzi do paranoi, paranoja prowadzi do ... takiego kodu.
Kevin J. Chase

Odpowiedzi:

131

Masz rację, w C jest tautologiczna. Zarówno twoje szczególne trójskładnikowe warunki warunkowe, jak i (1 > 0) są typu int.

Ale miałoby to znaczenie w C ++, w niektórych osobliwych przypadkach narożnych (np. Jako parametry przeciążonych funkcji), ponieważ twoje trójskładnikowe wyrażenie warunkowe jest typu int, a (1 > 0)jest typu bool.

Domyślam się, że autor przemyślał to, mając na uwadze zachowanie zgodności z C ++.

Batszeba
źródło
2
Myślałem, że bool <-> intkonwersje są niejawne w C ++ przez §4.7 / 4 ze standardu (konwersja całkowa), więc jakie to ma znaczenie?
Motun
70
Rozważ dwa przeciążenia funkcji foo, z których jeden przyjmuje a const bool&drugi const int&. Jeden z nich płaci, a drugi formatuje dysk twardy. W takim przypadku warto się upewnić, że wywołujesz prawidłowe przeciążenie.
Batszeba
3
czy nie byłoby bardziej oczywiste, aby zająć się tym przypadkiem poprzez rzutowanie wyniku na intzamiast używania trójskładnika?
martinkunev
18
@Bathsheba Chociaż jest to uzasadniony przypadek narożny, każdy programista, który używa integralnych przeciążeń do implementacji takiego niespójnego zachowania, jest w pełni zły.
JAB
7
@JAB: Nie musisz być zły, po prostu musisz popełnić (powszechny) błąd, pisząc fragment kodu, który przypadkowo robi dwie różne rzeczy (lub, co gorsza, wywołuje niezdefiniowane zachowanie ) w zależności od typu integralnego i mieć nieszczęście zrobienia tego w miejscu, które może wywołać radykalnie różne ścieżki kodu.
28

Istnieją narzędzia do lintingu, które uważają, że wynik porównania jest logiczny i nie można ich używać bezpośrednio w arytmetyce.

Nie nazywanie nazwisk ani wskazywanie palcami, ale PC-lint jest takim narzędziem do robienia kłaczków .

Nie mówię, że mają rację, ale jest to możliwe wyjaśnienie, dlaczego kod został napisany w ten sposób.

rozwijać
źródło
10
Not to name names or point any fingers,ale zrobiłeś jedno i drugie, lol.
StackOverflowed
19

Czasami zobaczysz to w bardzo starym kodzie, sprzed istnienia standardu C do przeliterowania, którego wynikiem (x > y)jest numeryczna 1 lub 0; niektóre procesory wolałyby zamiast tego oszacować to na -1 lub 0, a niektóre bardzo stare kompilatory mogły po prostu to zrobić, więc niektórzy programiści uznali, że potrzebują dodatkowej ochrony.

Będziesz czasami zobaczyć także to, ponieważ podobne wyrażenia nie koniecznie ocenić na numerycznej 1 lub 0. Na przykład w

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) ? 1 : 0)

&wyrażenie wewnętrzne ma wartość 0 lub wartość liczbową F_DO_GRENFELZ, która prawdopodobnie nie jest 1, więc ? 1 : 0służy do kanonizacji. Osobiście uważam, że jaśniej jest napisać to jako

#define GRENFELZ_P(flags) (((flags) & F_DO_GRENFELZ) != 0)

ale rozsądni ludzie mogą się nie zgodzić. Jeśli miał całą masę nich w rzędzie, testując różne rodzaje wyrażeń, ktoś zdecydowaliśmy, że to było bardziej linkujących umieścić ? 1 : 0na końcu wszyscy z nich, niż martwić się, które z nich rzeczywiście potrzebne.

zwol
źródło
W sumie wolę używać !!( expr )do kanonizacji wartości logicznej, ale przyznam, że jest to mylące, jeśli nie jesteś z nią zaznajomiony.
PJTraill
1
@PJTraill Za każdym razem, gdy wstawiasz spacje wewnątrz nawiasów, Bóg zabija kotka. Proszę. Pomyśl o kociętach.
zwol
To jest najlepszy powód, dla którego słyszałem, aby nie umieszczać spacji w nawiasach w programie C.
PJTraill
15

W kodzie SDK jest błąd, a trójnik prawdopodobnie nie byłby w stanie go naprawić.

Będąc makrem, argumenty (alpha_char) mogą być dowolnym wyrażeniem i powinny być umieszczone w nawiasach, ponieważ wyrażenia takie jak „A” i „c” nie przejdą testu.

#define IS_LOWER( x ) ( ( (x >= 'a') && (x <= 'z') ) ?  1 : 0 )
std::cout << IS_LOWER('A' && 'c');
**1**
std::cout << IS_LOWER('c' && 'A');
**0**

Dlatego w rozwinięciu zawsze należy umieszczać argumenty makr w nawiasach.

W twoim przykładzie (ale z parametrami) oba są błędne.

#define FOO(x) (x > 0)
#define BAR(x) ((x > 0) ? 1 : 0)

Najbardziej poprawnie zostałyby zastąpione przez

#define BIM(x) ((x) > 0)

@CiaPan To świetny punkt w następującym komentarzu, który mówi, że użycie parametru więcej niż jeden raz prowadzi do nieokreślonych rezultatów. Na przykład

#define IS_LOWER( x ) (((x) >= 'a') && ((x) <= 'z'))
char ch = 'y';
std::cout << IS_LOWER(ch++);
**1** 
**BUT ch is now '{'**
Konchog
źródło
4
Innym błędem jest to, że parametr jest używany dwukrotnie, więc argument z efektami ubocznymi doprowadzi do nieprzewidywalnych rezultatów: IS_LOWER(++ var)może zwiększać się varraz lub dwa razy, dodatkowo może nie zauważyć i rozpoznać małe litery, 'z'jeśli varbyło 'y'przed wywołaniem makra. Dlatego należy unikać takich makr lub po prostu przekazywać argument do funkcji.
CiaPan
5

W C to nie ma znaczenia. Wyrażenia logiczne w C mają typ inti wartość albo 0albo 1, więc

ConditionalExpr ? 1 : 0

nie ma żadnego efektu.

W C ++ jest to efektywne rzutowanie na int, ponieważ wyrażenia warunkowe w C ++ mają typ bool.

#include <stdio.h>
#include <stdbool.h>

#ifndef __cplusplus

#define print_type(X) _Generic(X, int: puts("int"), bool: puts("bool") );

#else
template<class T>
int print_type(T const& x);
template<> int print_type<>(int const& x) { return puts("int"); }
template<> int print_type<>(bool const& x) { return puts("bool"); }


#endif

int main()
{
    print_type(1);
    print_type(1 > 0);
    print_type(1 > 0 ? 1 : 0);

/*c++ output:
  int 
  int 
  int

  cc output:
  int
  bool
  int
*/

}

Możliwe jest również, że żaden efekt nie był zamierzony, a autor po prostu pomyślał, że dzięki temu kod jest jaśniejszy.

PSkocik
źródło
Swoją drogą, myślę, że C powinien podążać za pakietem i tworzyć wyrażenia logiczne _Bool, teraz, gdy C ma _Booli _Generic. Nie powinno to zepsuć zbyt wiele kodu, biorąc pod uwagę, że wszystkie mniejsze typy i tak są automatycznie przenoszone do intwiększości kontekstów.
PSkocik
5

Jednym prostym wyjaśnieniem jest to, że niektórzy ludzie albo nie rozumieją, że warunek zwróciłby tę samą wartość w C, albo myślą, że pisanie jest czystsze ((a>b)?1:0).

To wyjaśnia, dlaczego niektórzy używają podobnych konstrukcji również w językach z odpowiednimi parametrami logicznymi, którymi byłyby w C-składni (a>b)?true:false).

To również wyjaśnia, dlaczego nie powinieneś niepotrzebnie zmieniać tego makra.

Hans Olsson
źródło
0

Być może bycie oprogramowaniem wbudowanym dałoby kilka wskazówek. Być może jest wiele makr napisanych w tym stylu, aby łatwo wskazać, że linie ACTI używają logiki bezpośredniej zamiast logiki odwróconej.

J.Guarin
źródło