Sind C ++ - Aufzählungen signiert oder nicht signiert?

107

Sind C ++ - Aufzählungen signiert oder nicht signiert? Und ist es sicher, eine Eingabe zu validieren, indem Sie überprüfen, ob sie <= Ihr Maximalwert ist, und> = Ihren Minimalwert weglassen (vorausgesetzt, Sie haben bei 0 begonnen und um 1 erhöht)?

Matt
quelle
Wenn wir einen Aufzählungstyp in einem Kontext verwenden, für den das Vorzeichen erforderlich ist, sprechen wir tatsächlich davon, die Aufzählung implizit in einen integralen Typ umzuwandeln. Der C ++ 03-Standard besagt, dass dies durch Integral Promotion erfolgt, was nichts mit dem zugrunde liegenden Typ der Aufzählung zu tun hat. Ich verstehe also nicht, warum in jeder Antwort hier erwähnt wird, dass der zugrunde liegende Typ nicht durch den Standard definiert ist. Ich habe das erwartete Verhalten hier beschrieben: stackoverflow.com/questions/24802322/…
JavaMan

Antworten:

60

Sie sollten sich nicht auf eine bestimmte Darstellung verlassen. Lesen Sie den folgenden Link . Außerdem besagt der Standard, dass implementierungsdefiniert ist, welcher Integraltyp als zugrunde liegender Typ für eine Aufzählung verwendet wird, mit der Ausnahme, dass er nicht größer als int sein darf, es sei denn, ein Wert kann nicht in int oder ein vorzeichenloses int passen.

Kurz gesagt: Sie können sich nicht darauf verlassen, dass eine Aufzählung signiert oder nicht signiert ist.

zvrba
quelle
28
Die Antwort von Michael Burr (die den Standard zitiert) impliziert tatsächlich, dass Sie sich darauf verlassen können, dass er signiert wird, wenn Sie einen Aufzählungswert als negativ definieren, da der Typ "alle in der Aufzählung definierten Aufzählungswerte darstellen kann".
Samuel Harmer
101

Gehen wir zur Quelle. Im Dokument zum C ++ 03-Standard (ISO / IEC 14882: 2003) heißt es in 7.2-5 (Aufzählungserklärungen):

Der zugrunde liegende Typ einer Aufzählung ist ein integraler Typ, der alle in der Aufzählung definierten Aufzählungswerte darstellen kann. Es ist implementierungsdefiniert, welcher Integraltyp als zugrunde liegender Typ für eine Aufzählung verwendet wird, mit der Ausnahme, dass der zugrunde liegende Typ nicht größer als int sein darf, es sei denn, der Wert eines Enumerators kann nicht in ein int oder ein vorzeichenloses int passen.

Kurz gesagt, Ihr Compiler kann wählen (wenn Sie für einige Ihrer Aufzählungswerte negative Zahlen haben, wird diese natürlich signiert).

Michael Burr
quelle
Wie können wir Compiler-Vermutungen vermeiden und ihm sagen, dass er einen zugrunde liegenden vorzeichenlosen Typ verwenden soll, wenn alle Aufzählungswerte kleine, positive ganze Zahlen sind? (Wir fangen einen UBsan-Befund ab, weil der Compiler ein int auswählt und int einen Überlauf erleidet. Der Wert ist vorzeichenlos und positiv, und unsere Verwendung hängt vom vorzeichenlosen Wrap ab, um ein Dekrement oder einen "negativen Schritt" bereitzustellen.)
JWW
@jww - das hängt davon ab, welchen Compiler Sie genau verwenden, denke ich. Da der Standard den zugrunde liegenden Typ nicht vorschreibt und dies der Implementierung überlässt, müssen Sie in der Dokumentation Ihres Tools nachsehen, ob diese Option überhaupt möglich ist. Wenn Sie ein bestimmtes Verhalten in Ihrem Code garantieren möchten, können Sie das Enum-Mitglied, das Sie im Ausdruck verwenden, umwandeln.
Ysap
22

Sie sollten sich nicht darauf verlassen, dass sie signiert oder nicht signiert sind. Wenn Sie sie explizit signiert oder nicht signiert machen möchten, können Sie Folgendes verwenden:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum
Adam Rosenfield
quelle
11
Nur in Zukunft C ++ 0x Standard.
dalle
3
@dalle Microsoft Compilator erlaubt auch eingegebene Aufzählungen msdn.microsoft.com/en-us/library/2dzy4k6e(v=vs.80).aspx
teodozjan
15

Sie sollten sich nicht darauf verlassen, dass es signiert oder nicht signiert ist. Gemäß dem Standard ist implementierungsdefiniert, welcher Integraltyp als zugrunde liegender Typ für eine Aufzählung verwendet wird. In den meisten Implementierungen handelt es sich jedoch um eine vorzeichenbehaftete Ganzzahl.

In C ++ 0x werden stark typisierte Aufzählungen hinzugefügt, mit denen Sie den Typ einer Aufzählung angeben können, z.

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

Selbst jetzt kann eine einfache Validierung erreicht werden, indem die Aufzählung als Variablen- oder Parametertyp wie folgt verwendet wird:

enum Fruit { Apple, Banana };

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit
                    // even though it has the same value as banana.
Matt
quelle
Ich denke, Ihr zweites Beispiel ist ein bisschen verwirrt :)
Miral
5

Der Compiler kann entscheiden, ob Aufzählungen signiert oder nicht signiert sind.

Eine andere Methode zum Überprüfen von Aufzählungen besteht darin, die Aufzählung selbst als Variablentyp zu verwenden. Beispielsweise:

enum Fruit
{
    Apple = 0,
    Banana,
    Pineapple,
    Orange,
    Kumquat
};

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit even though it has the same value as banana.
Cristián Romo
quelle
5

Sogar einige alte Antworten haben 44 positive Stimmen erhalten, ich stimme allen nicht zu. Kurz gesagt, ich denke nicht, dass wir uns um das kümmern solltenunderlying type die Aufzählung .

Zunächst einmal ist der C ++ 03-Aufzählungstyp ein eigenständiger Typ ohne Vorzeichenkonzept. Seit ab C ++ 03 Standarddcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

Wenn wir also über das Vorzeichen eines Aufzählungstyps sprechen, beispielsweise wenn wir zwei Aufzählungsoperanden mit dem <Operator vergleichen, sprechen wir tatsächlich über die implizite Konvertierung des Aufzählungstyps in einen ganzzahligen Typ. Es ist das Zeichen dieses integralen Typs, das zählt . Und wenn Sie enum in einen integralen Typ konvertieren, gilt diese Aussage:

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

Und anscheinend hat der zugrunde liegende Typ der Aufzählung nichts mit der integralen Förderung zu tun. Da der Standard Integral Promotion wie folgt definiert:

4.5 Integral promotions conv.prom
.. An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2: int, unsigned int, long, or unsigned long.

Ob ein Aufzählungstyp wird signed intoder unsigned intdavon abhängt, ob er signed intalle Werte der definierten Enumeratoren enthalten kann, nicht den zugrunde liegenden Typ der Aufzählung.

Siehe meine verwandte Frage Zeichen des C ++ - Aufzählungstyps nach Konvertierung in einen integralen Typ falsch

JavaMan
quelle
Es ist wichtig, wenn Sie mit kompilieren -Wsign-conversion. Wir verwenden es, um unbeabsichtigte Fehler in unserem Code zu erkennen. Aber +1, um den Standard zu zitieren und darauf hinzuweisen, dass einer Aufzählung kein Typ ( signedversus unsigned) zugeordnet ist.
JWW
4

In Zukunft werden mit C ++ 0x stark typisierte Aufzählungen verfügbar sein und mehrere Vorteile haben (z. B. Typensicherheit, explizite zugrunde liegende Typen oder explizites Scoping). Damit können Sie sich des Zeichens des Typs besser sicher sein.

Kris Kumler
quelle
4

Zusätzlich zu dem, was andere bereits über signiert / nicht signiert gesagt haben, sagt der Standard Folgendes über den Bereich eines aufgezählten Typs:

7.2 (6): "Bei einer Aufzählung, bei der e (min) der kleinste Aufzähler und e (max) der größte ist, sind die Werte der Aufzählung die Werte des zugrunde liegenden Typs im Bereich b (min) bis b (max ), wobei b (min) und b (max) jeweils die kleinsten und größten Werte des kleinsten Bitfelds sind, in dem e (min) und e (max) gespeichert werden können. Es ist möglich, eine Aufzählung zu definieren, deren Werte nicht definiert sind von einem seiner Aufzähler. "

Also zum Beispiel:

enum { A = 1, B = 4};

definiert einen Aufzählungstyp, wobei e (min) 1 und e (max) 4 ist. Wenn der zugrunde liegende Typ int signiert ist, hat das kleinste erforderliche Bitfeld 4 Bits, und wenn Ints in Ihrer Implementierung Zweierkomplement sind, ist der gültige Bereich von Die Aufzählung ist -8 bis 7. Wenn der zugrunde liegende Typ ohne Vorzeichen ist, hat er 3 Bits und der Bereich liegt zwischen 0 und 7. Überprüfen Sie Ihre Compiler-Dokumentation, wenn Sie sich interessieren (zum Beispiel, wenn Sie andere Integralwerte als Enumeratoren in die umwandeln möchten Aufzählungstyp, dann müssen Sie wissen, ob der Wert im Bereich der Aufzählung liegt oder nicht - wenn nicht, ist der resultierende Aufzählungswert nicht angegeben).

Ob diese Werte eine gültige Eingabe für Ihre Funktion sind, hängt möglicherweise davon ab, ob es sich um gültige Werte vom Aufzählungstyp handelt. Ihr Prüfcode ist wahrscheinlich eher um den ersteren als um den letzteren besorgt und sollte daher in diesem Beispiel zumindest> = A und <= B prüfen.

Steve Jessop
quelle
0

Überprüfen Sie dies mit den std::is_signed<std::underlying_typeAufzählungen mit + Gültigkeitsbereichint

https://en.cppreference.com/w/cpp/language/enum impliziert:

main.cpp

#include <cassert>
#include <iostream>
#include <type_traits>

enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};

int main() {
    // Implementation defined, let's find out.
    std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;

    // Guaranteed. Scoped defaults to int.
    assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));

    // Guaranteed. We set it ourselves.
    assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}

GitHub stromaufwärts .

Kompilieren und ausführen:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

Ausgabe:

0

Getestet unter Ubuntu 16.04, GCC 6.4.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle