Warum wird der ternäre Operator verwendet, um 1 und 0 in einem Makro zu definieren?

79

Ich verwende ein SDK für ein eingebettetes Projekt. In diesem Quellcode habe ich einen Code gefunden, den ich zumindest eigenartig fand. An vielen Stellen im SDK gibt es Quellcode in diesem Format:

#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 )

Macht die Verwendung des ternären Operators hier einen Unterschied?

Ist nicht

#define FOO (1 > 0)

das Gleiche wie

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

?

Ich habe versucht, es mit zu bewerten

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

und erhalten Sie das Ergebnis 1, so scheint es, dass sie gleich sind. Gibt es einen Grund, den Code so zu schreiben, wie sie es getan haben?

Viktor S.
quelle
8
Nein, es gibt keinen Grund. Du hast recht.
Art
29
Teilweise außerhalb des Themas: Wann hört der Wahnsinn auf, den Präprozessor zu benutzen? Hier besteht die Möglichkeit einer Mehrfachbewertung der beteiligten Funktionen. Nur unnötig.
Stefan
3
Manchmal ist es auch schön, explizit zu sein. Der ternäre Operator macht hier auf einen Blick deutlich, dass der Zweck des Makros darin besteht, einen Booleschen Wert zurückzugeben.
Pipe
5
Zumindest sollten die Makros (alpha_char)stattdessen verwendet werden alpha_char, nur um sicherzustellen, dass es nicht kaputt geht, wenn jemand etwas Verrücktes versuchtATCI_IS_LOWER(true || -1) .
Justin Time - Stellen Sie Monica am
5
Sieht aus wie die Art von CI, die vor langer Zeit geschrieben wurde. Ich war von Pascal nach C gekommen, das einen speziellen booleanTyp hatte, also verschwendete ich unzählige Zeit damit, die Schrecken if (n)zu ändern , if (0 != n)und fügte wahrscheinlich eine zweifelhafte Besetzung hinzu, "um sicherzugehen". Ich bin mir sicher, dass ich auch kugelsichere Ungleichungen mag if (a < b) .... Sicher , es sah aus wie Pascal if a < b then ..., aber ich wusste , dass Cs < war keine , booleansondern ein int, und eine intkönnte sein , fast alles ! Angst führt zu Vergoldung, Vergoldung führt zu Paranoia, Paranoia führt zu ... Code wie diesem.
Kevin J. Chase

Antworten:

131

Sie haben Recht, in C ist es tautolog. Sowohl Ihre besondere ternäre Bedingung als auch (1 > 0) sind vom Typ int.

Aber es wäre in C ++ Rolle , obwohl in einigen neugierigen Ecke Fällen (zB als Parameter überladene Funktionen), da Ihr dreifach konditionaler Ausdruck vom Typ ist int, während (1 > 0)vom Typ bool.

Ich vermute, dass der Autor einige Überlegungen angestellt hat, um die C ++ - Kompatibilität zu erhalten.

Bathseba
quelle
2
Ich dachte, bool <-> intKonvertierungen sind in C ++ gemäß §4.7 / 4 aus dem Standard (integrale Konvertierung) impliziert. Wie wäre es also wichtig?
Motun
70
Betrachten Sie zwei Überladungen einer Funktion foo, von denen eine eine und const bool&die andere eine übernimmt const int&. Einer von ihnen bezahlt Sie, der andere formatiert Ihre Festplatte neu. In diesem Fall möchten Sie möglicherweise sicherstellen, dass Sie die richtige Überlastung aufrufen.
Bathseba
3
Wäre es nicht offensichtlicher, diesen Fall durch Casting des Ergebnisses zu behandeln, intanstatt das Ternäre zu verwenden?
Martinkunev
18
@Bathsheba Obwohl dies ein legitimer Eckfall ist, ist jeder Programmierer, der integrale Überladungen verwendet, um solch inkonsistentes Verhalten zu implementieren, völlig böse.
JAB
7
@JAB: Du musst nicht böse sein, du musst nur den (häufigen) Fehler machen, einen Code zu schreiben, der versehentlich zwei verschiedene Dinge tut (oder schlimmer noch, undefiniertes Verhalten aufruft ), abhängig vom integralen Typ, und das haben Unglück, dies an einem Ort zu tun, der radikal andere Codepfade auslösen kann.
28

Es gibt Flusenwerkzeuge, die der Meinung sind, dass das Ergebnis eines Vergleichs boolesch ist und nicht direkt in der Arithmetik verwendet werden kann.

Keine Namen zu nennen oder mit den Fingern zu zeigen, aber PC-Lint ist ein solches Flusenwerkzeug .

Ich sage nicht, dass sie Recht haben, aber es ist eine mögliche Erklärung dafür, warum der Code so geschrieben wurde.

entspannen
quelle
10
Not to name names or point any fingers,aber du hast beides getan, lol.
StackOverflowed
19

Sie werden dies manchmal in sehr altem Code sehen, bevor es einen C-Standard gab, (x > y)der mit der Ziffer 1 oder 0 bewertet wurde. Einige CPUs würden dies lieber auf -1 oder 0 auswerten, und einige sehr alte Compiler sind möglicherweise gerade gefolgt, sodass einige Programmierer der Meinung waren, dass sie die zusätzliche Abwehr benötigen.

Sie werden dies manchmal auch sehen, weil ähnliche Ausdrücke nicht unbedingt als numerisch 1 oder 0 ausgewertet werden. Zum Beispiel in

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

Der innere &Ausdruck ergibt entweder 0 oder den numerischen Wert von F_DO_GRENFELZ, der wahrscheinlich nicht 1 ist, und ? 1 : 0dient daher zur Kanonisierung. Ich persönlich denke, es ist klarer, das als zu schreiben

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

aber vernünftige Leute können nicht zustimmen. Wenn Sie eine ganze Reihe von Ausdrücken hintereinander hatten und verschiedene Arten von Ausdrücken testeten, hätte jemand vielleicht entschieden, dass es besser zu warten ist, alle zu? 1 : 0 beenden , als sich Gedanken darüber zu machen, welche tatsächlich benötigt werden.

zwol
quelle
Im Großen und Ganzen bevorzuge ich die !!( expr )Kanonisierung eines Booleschen Werts, aber ich gebe zu, dass es verwirrend ist, wenn Sie nicht damit vertraut sind.
PJTraill
1
@PJTraill Jedes Mal, wenn Sie Leerzeichen in Ihre Klammern setzen, tötet Gott ein Kätzchen. Bitte. Denken Sie an die Kätzchen.
zwol
Das ist der beste Grund, warum ich gehört habe, in einem C-Programm keine Leerzeichen in Klammern zu setzen.
PJTraill
15

Es gibt einen Fehler im SDK-Code, und der Ternär war wahrscheinlich ein Problem, um ihn zu beheben.

Als Makro können die Argumente (alpha_char) ein beliebiger Ausdruck sein und sollten in Klammern gesetzt werden, da Ausdrücke wie 'A' && 'c' den Test nicht bestehen.

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

Aus diesem Grund sollten Makroargumente in der Erweiterung immer in Klammern gesetzt werden.

In Ihrem Beispiel (aber mit Parametern) sind beide fehlerhaft.

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

Sie würden am korrektesten durch ersetzt

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

@CiaPan In dem folgenden Kommentar wird besonders darauf hingewiesen, dass die mehrmalige Verwendung eines Parameters zu undefinierbaren Ergebnissen führt. Zum Beispiel

#define IS_LOWER( x ) (((x) >= 'a') && ((x) <= 'z'))
char ch = 'y';
std::cout << IS_LOWER(ch++);
**1** 
**BUT ch is now '{'**
Konchog
quelle
4
Ein weiterer Fehler ist , dass der Parameter zweimal verwendet wird, so ein Argument , mit Nebenwirkungen zu unvorhersehbaren Ergebnissen führen wird: IS_LOWER(++ var)kann erhöht varein- oder zweimal, zusätzlich kann es nicht zur Kenntnis und erkennt Klein 'z'wenn varwar 'y'vor dem Makroaufruf. Deshalb sollten solche Makros vermieden werden oder das Argument einfach an eine Funktion weiterleiten.
CiaPan
5

In C spielt es keine Rolle. Boolesche Ausdrücke in C haben einen Typ intund einen Wert, der entweder 0oder 1so ist

ConditionalExpr ? 1 : 0

hat keine Wirkung.

In C ++ ist es effektiv eine Umwandlung in int, da bedingte Ausdrücke in C ++ vom Typ sind 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
*/

}

Es ist auch möglich, dass kein Effekt beabsichtigt war, und der Autor dachte einfach, dass dies den Code klarer macht.

PSkocik
quelle
Übrigens denke ich, C sollte der Suite folgen und boolesche Ausdrücke machen _Bool, jetzt wo C _Boolund hat _Generic. Es sollte nicht viel Code brechen, da sich alle kleineren Typen intin den meisten Kontexten sowieso automatisch weiterentwickeln .
PSkocik
5

Eine einfache Erklärung ist, dass einige Leute entweder nicht verstehen, dass eine Bedingung den gleichen Wert in C zurückgeben würde, oder sie denken, dass das Schreiben sauberer ist ((a>b)?1:0) .

Das erklärt, warum einige ähnliche Konstrukte auch in Sprachen mit richtigen Booleschen Werten verwenden, was in der C-Syntax der Fall wäre (a>b)?true:false) .

Dies erklärt auch, warum Sie dieses Makro nicht unnötig ändern sollten.

Hans Olsson
quelle
0

Vielleicht würde eine eingebettete Software einige Hinweise geben. Möglicherweise sind viele Makros mit diesem Stil geschrieben, um leicht darauf hinzuweisen, dass ACTI-Zeilen eher direkte Logik als invertierte Logik verwenden.

J. Guarin
quelle