Betrachten Sie die folgende enum- und switch-Anweisung:
typedef enum {
MaskValueUno,
MaskValueDos
} testingMask;
void myFunction(testingMask theMask) {
switch (theMask) {
case MaskValueUno: {}// deal with it
case MaskValueDos: {}// deal with it
default: {} //deal with an unexpected or uninitialized value
}
};
Ich bin ein Objective-C-Programmierer, aber ich habe dies für ein breiteres Publikum in reinem C geschrieben.
Clang / LLVM 4.1 mit -Alles warnt mich in der Standardzeile:
Standardbeschriftung im Schalter, die alle Aufzählungswerte abdeckt
Jetzt kann ich irgendwie nachvollziehen, warum dies so ist: In einer perfekten Welt würden die einzigen Werte, die in das Argument theMask
eingegeben werden, in der Aufzählung stehen, sodass keine Standardwerte erforderlich sind. Aber was ist, wenn ein Hack auftaucht und ein nicht initialisiertes Int in meine schöne Funktion wirft? Meine Funktion wird als Ablage in der Bibliothek bereitgestellt, und ich habe keine Kontrolle darüber, was dort hineingelangen könnte. Verwenden default
ist eine sehr ordentliche Art, damit umzugehen.
Warum halten die LLVM-Götter dieses Verhalten für ihres teuflischen Geräts unwürdig? Sollte ich dem eine if-Anweisung voranstellen, um das Argument zu überprüfen?
quelle
"Pro tip: Try setting the -Weverything flag and checking the “Treat Warnings as Errors” box your build settings. This turns on Hard Mode in Xcode."
.-Weverything
kann nützlich sein, aber seien Sie vorsichtig, wenn Sie Ihren Code zu stark verändern, um damit umzugehen. Einige dieser Warnungen sind nicht nur wertlos, sondern auch kontraproduktiv und sollten am besten deaktiviert werden. (In der Tat, das ist der Anwendungsfall für-Weverything
: Beginnen Sie damit und deaktivieren Sie, was keinen Sinn ergibt.)Antworten:
Hier ist eine Version, die weder unter der Meldung des Problem-Clangs noch unter der Version leidet, vor der Sie sich schützen:
Killian hat bereits erklärt, warum Clang die Warnung ausgibt: Wenn Sie die Aufzählung verlängern, fallen Sie in den Standardfall, der wahrscheinlich nicht Ihren Wünschen entspricht. Das Richtige ist, den Standardfall zu entfernen und Warnungen für nicht behandelte Bedingungen abzurufen .
Jetzt befürchten Sie, dass jemand Ihre Funktion mit einem Wert aufrufen könnte, der außerhalb der Aufzählung liegt. Das hört sich so an, als würde die Funktionsvoraussetzung nicht erfüllt: Es ist dokumentiert, dass ein Wert von der
testingMask
Aufzählung erwartet wird, aber der Programmierer hat etwas anderes übergeben. Machen Sie also einen Programmierfehler mitassert()
(oderNSCAssert()
wie Sie sagten, Sie verwenden Objective-C). Lassen Sie Ihr Programm mit einer Meldung abstürzen, die erklärt, dass der Programmierer es falsch macht, wenn der Programmierer es falsch macht.quelle
return;
und einenassert(false);
am Ende hinzuzufügen (anstatt mich selbst zu wiederholen, indem ich die gesetzlichen Aufzählungen in einem Anfangsbuchstabenassert()
und in dem aufführeswitch
).Ein
default
Etikett hier ist ein Indikator dafür, dass Sie sich nicht sicher sind, was Sie erwarten. Da Sie alle möglichenenum
Werte explizit ausgeschöpft haben,default
kann das nicht ausgeführt werden, und Sie brauchen es auch nicht, um sich vor zukünftigen Änderungen zu schützen, denn wenn Sie das erweiternenum
, würde das Konstrukt bereits eine Warnung erzeugen.Der Compiler merkt also, dass Sie alle Grundlagen abgedeckt haben, denkt aber anscheinend, dass Sie dies nicht getan haben, und das ist immer ein schlechtes Zeichen. Indem
switch
Sie das Formular mit minimalem Aufwand in das erwartete Format ändern , zeigen Sie dem Compiler, dass Sie anscheinend genau das tun, was Sie tatsächlich tun, und Sie wissen es.quelle
Clang ist verwirrt und hat eine Standardaussage, dass es eine sehr gute Übung gibt. Sie wird als defensive Programmierung bezeichnet und gilt als gute Programmierpraxis (1). Es wird häufig in unternehmenskritischen Systemen verwendet, möglicherweise jedoch nicht in der Desktop-Programmierung.
Der Zweck der defensiven Programmierung besteht darin, unerwartete Fehler aufzufangen, die theoretisch niemals auftreten würden. Ein solcher unerwarteter Fehler ist nicht notwendigerweise der Programmierer, der der Funktion eine falsche Eingabe oder sogar einen "bösen Hack" gibt. Es ist wahrscheinlicher, dass dies durch eine beschädigte Variable verursacht wurde: Pufferüberläufe, Stapelüberlauf, außer Kontrolle geratener Code und ähnliche Fehler, die nicht mit Ihrer Funktion zusammenhängen, könnten dies verursachen. Bei eingebetteten Systemen können sich Variablen möglicherweise aufgrund von EMI ändern, insbesondere wenn Sie externe RAM-Schaltkreise verwenden.
Was Sie in die Standardanweisung schreiben sollten ... Wenn Sie den Verdacht haben, dass das Programm am Ende durcheinander geraten ist, benötigen Sie eine Art Fehlerbehandlung. In vielen Fällen können Sie wahrscheinlich einfach eine leere Anweisung mit einem Kommentar hinzufügen: "Unerwartet, aber egal" usw., um zu zeigen, dass Sie über die unwahrscheinliche Situation nachgedacht haben.
(1) MISRA-C: 2004 15.3.
quelle
-Weverything
aktiviert auch hier jede Warnung und nicht eine Auswahl, die für einen bestimmten Anwendungsfall geeignet ist. Nicht deinem Geschmack zu entsprechen ist nicht das Gleiche wie Verwirrung.Besser noch:
Dies ist weniger fehleranfällig beim Hinzufügen von Elementen zur Aufzählung. Sie können den Test für> = 0 überspringen, wenn Sie Ihre Enum-Werte vorzeichenlos machen. Diese Methode funktioniert nur, wenn Ihre Aufzählungswerte keine Lücken aufweisen. Dies ist jedoch häufig der Fall.
quelle
Dann bekommst du Undefiniertes Verhalten und dein
default
Wille ist bedeutungslos. Sie können möglicherweise nichts tun, um dies zu verbessern.Lass mich klarer sein. Sobald jemand eine nicht initialisierte
int
Funktion in Ihre Funktion übergibt , ist dies undefiniertes Verhalten. Ihre Funktion könnte das Problem des Anhaltens lösen, und es spielt keine Rolle. Es ist UB. Es gibt nichts, was Sie tun können, wenn UB einmal aufgerufen wurde.quelle
Die Standardanweisung würde nicht unbedingt helfen. Wenn sich der Schalter über einer Aufzählung befindet, führt jeder Wert, der nicht in der Aufzählung definiert ist, undefiniertes Verhalten aus.
Nach allem, was Sie wissen, kann der Compiler diesen Switch (mit der Standardeinstellung) wie folgt kompilieren:
Sobald Sie undefiniertes Verhalten auslösen, gibt es kein Zurück mehr.
quelle
enum
Typs dem Wertebereich seines zugrunde liegenden Integer-Typs (der implementierungsdefiniert ist und daher selbstkonsistent sein muss).Ich bevorzuge es auch,
default:
in allen Fällen eine zu haben . Ich bin wie immer zu spät zur Party, aber ... ein paar andere Gedanken, die ich oben nicht gesehen habe:-Werror
) kommt von-Wcovered-switch-default
(von,-Weverything
aber nicht von-Wall
). Wenn Ihre moralische Flexibilität es Ihnen erlaubt, bestimmte Warnungen auszuschalten (z. B. bestimmte Dinge aus-Wall
oder herauszuschneiden-Weverything
), erwägen Sie, sie zu werfen-Wno-covered-switch-default
(oder zu-Wno-error=covered-switch-default
verwenden-Werror
), und im Allgemeinen-Wno-...
für andere Warnungen, die Sie als unangenehm empfinden.gcc
(und allgemeineres Verhalten inclang
), konsultiertgcc
Manpage-Wswitch
,-Wswitch-enum
,-Wswitch-default
für (verschiedene) Verhalten in ähnlichen Situationen von Aufzählungstypen in switch - Anweisungen.Ich mag diese Warnung auch nicht im Konzept, und ich mag ihren Wortlaut nicht; Für mich deuten die Wörter aus der Warnung ("Standardbeschriftung ... deckt alle ... Werte ab") darauf hin, dass der
default:
Fall immer ausgeführt wird, wie zAuf dem ersten Lesung, ist das , was ich dachte , dass Sie in ausgeführt wurden - , dass Ihr
default:
Fall immer dann ausgeführt werden würde , weil es keine gibtbreak;
oderreturn;
oder ähnliches. Dieses Konzept ähnelt (meinem Ohr) anderen (wenn auch gelegentlich hilfreichen) Kommentaren im Nanny-Stil, aus denen es hervorgehtclang
. Wennfoo == 1
, werden beide Funktionen ausgeführt; Ihr Code oben hat dieses Verhalten. Das heißt, nicht ausbrechen, nur wenn Sie möglicherweise weiterhin Code aus nachfolgenden Fällen ausführen möchten! Dies scheint jedoch nicht Ihr Problem zu sein.Auf die Gefahr, pedantisch zu sein, einige andere Gedanken zur Vollständigkeit:
int
oder etwas zu dieser Funktion, die ausdrücklich sind eigene Art beabsichtigt , zu konsumieren, sollten Sie Ihre Compiler Sie schützen gleich gut in dieser Situation mit einer aggressiven Warnung oder einen Fehler. ABER das tut es nicht! (Das heißt, es scheint, dass zumindestgcc
undclang
nicht dieenum
Typüberprüfung, aber ich höre, dass dies dericc
Fall ist ). Da Sie nicht bekommen Typ -Sicherheit, könnten Sie erhalten Wert -safety wie oben erörtert. Andernfalls sollten Sie, wie in TFA vorgeschlagen, einstruct
oder etwas in Betracht ziehen , das Typensicherheit bietet.enum
Namen zu erstellenMaskValueIllegal
und diesencase
in Ihrem Namen nicht zu unterstützenswitch
. Das würde gegessen vondefault:
(zusätzlich zu jedem anderen verrückten Wert)Es lebe die defensive Codierung!
quelle
Hier ist ein alternativer Vorschlag:
Das OP versucht, sich gegen den Fall zu schützen, dass jemand in einem Fall vorbeikommt, in
int
dem eine Aufzählung erwartet wird. Oder eher dort, wo jemand eine alte Bibliothek mit einem neueren Programm verknüpft hat, indem er einen neueren Header mit mehr Fällen verwendet.Warum nicht den Schalter wechseln, um das
int
Gehäuse zu handhaben ? Das Hinzufügen eines Cast vor dem Wert in der Option beseitigt die Warnung und gibt sogar einen Hinweis darauf, warum die Standardeinstellung existiert.Ich finde das viel weniger verwerflich als das
assert()
Testen jedes der möglichen Werte oder sogar die Annahme, dass der Bereich der Aufzählungswerte geordnet ist, damit ein einfacherer Test funktioniert. Dies ist nur eine hässliche Methode, um genau und schön das zu tun, was default tut.quelle