wake-up-neo.net

Ist es sicher, -1 zu verwenden, um alle Bits auf true zu setzen?

Ich habe gesehen, dass dieses Muster in C & C++ häufig verwendet wird.

unsigned int flags = -1;  // all bits are true

Ist dies ein guter portabler Weg, um dies zu erreichen? Oder verwendet 0xffffffff oder ~0 besser?

130
hyperlogic

Ich empfehle Ihnen, es genau so zu machen, wie Sie es gezeigt haben, da es das direkteste ist. Initialisiere auf -1 was immer funktioniert , unabhängig von der tatsächlichen Vorzeichendarstellung, während ~ hat manchmal ein überraschendes Verhalten, da Sie den richtigen Operandentyp haben müssen. Nur dann erhalten Sie den höchsten Wert eines unsigned Typs.

Betrachten Sie als Beispiel für eine mögliche Überraschung Folgendes:

unsigned long a = ~0u;

Es wird nicht unbedingt ein Muster mit allen Bits 1 in a gespeichert. Aber es wird zuerst ein Muster mit allen Bits 1 in einem unsigned int, und weisen Sie es dann a zu. Was passiert, wenn unsigned long hat mehr Bits als 1.

Und betrachten Sie diese, die in der Komplementdarstellung eines Nicht-Zweien fehlschlägt:

unsigned int a = ~0; // Should have done ~0u !

Der Grund dafür ist, dass ~0 muss alle Bits invertieren. Invertieren, das ergibt -1 auf einer Zweierkomplement-Maschine (das ist der Wert, den wir brauchen!), aber wird nicht ergeben -1 auf einer anderen Darstellung. Auf der eigenen Komplement-Maschine ergibt sich Null. Auf der eigenen Komplement-Maschine wird also a mit Null initialisiert.

Das, was Sie verstehen sollten, ist, dass es nur um Werte geht - nicht um Bits. Die Variable wird mit einem Wert initialisiert. Wenn Sie im Initialisierer die Bits der zur Initialisierung verwendeten Variablen ändern, wird der Wert entsprechend diesen Bits generiert. Der Wert, den Sie benötigen, um a auf den höchstmöglichen Wert zu initialisieren, ist -1 oder UINT_MAX. Die zweite hängt von der Art von a ab - Sie müssen ULONG_MAX für ein unsigned long. Das erste hängt jedoch nicht von seiner Art ab, und es ist eine gute Möglichkeit, den höchsten Wert zu erzielen.

Wir sprechen nicht darüber, ob -1 hat alle Bits eins (nicht immer). Und wir reden nicht darüber, ob ~0 hat alle Bits eins (hat es natürlich).

Es handelt sich jedoch um das Ergebnis der initialisierten Variablen flags. Und dafür nur -1 funktioniert mit jedem Typ und jeder Maschine.

152
  • unsigned int flags = -1; ist portabel.
  • unsigned int flags = ~0; ist nicht portierbar, da es sich auf eine Zweierkomplementdarstellung stützt.
  • unsigned int flags = 0xffffffff; ist nicht portierbar, da 32-Bit-Ints vorausgesetzt werden.

Wenn Sie alle Bits auf eine vom C-Standard garantierte Weise setzen möchten, verwenden Sie die erste.

48
Dingo

Ehrlich gesagt denke ich, dass alle FFFs besser lesbar sind. In Bezug auf den Kommentar, dass es sich um ein Antipattern handelt, würde ich argumentieren, dass Sie sich wahrscheinlich in einer Situation befinden, in der Sie sich sowieso für die Größe der Variablen interessieren, was so etwas wie Boost erforderlich machen würde :: uint16_t usw.

25
Doug T.

Eine Möglichkeit, die genannten Probleme zu vermeiden, besteht darin, einfach Folgendes zu tun:

unsigned int flags = 0;
flags = ~flags;

Tragbar und auf den Punkt.

17
hammar

Ich bin nicht sicher, ob die Verwendung eines vorzeichenlosen Int für Flags in C++ eine gute Idee ist. Was ist mit Bitset und dergleichen?

std::numeric_limit<unsigned int>::max() ist besser, weil 0xffffffff geht davon aus, dass int ohne Vorzeichen eine 32-Bit-Ganzzahl ist.

13
Edouard A.
unsigned int flags = -1;  // all bits are true

"Ist dies ein guter, tragbarer Weg, um dies zu erreichen?"

Tragbar? Ja.

Gut? mstritten, wie die Verwirrung in diesem Thread zeigt. Es sollte eine der Dimensionen sein, die wir für guten Code messen, wenn wir klar genug sind, dass Ihre Programmierkollegen den Code ohne Verwirrung verstehen können.

Diese Methode ist auch anfällig für Compiler-Warnungen. Um die Warnung zu umgehen, ohne Ihren Compiler zu lähmen, benötigen Sie eine explizite Besetzung. Beispielsweise,

unsigned int flags = static_cast<unsigned int>(-1);

Die explizite Besetzung erfordert, dass Sie auf den Zieltyp achten. Wenn Sie auf den Zieltyp achten, vermeiden Sie natürlich die Fallstricke der anderen Ansätze.

Mein Rat wäre, auf den Zieltyp zu achten und sicherzustellen, dass es keine impliziten Konvertierungen gibt. Beispielsweise:

unsigned int flags1 = UINT_MAX;
unsigned int flags2 = ~static_cast<unsigned int>(0);
unsigned long flags3 = ULONG_MAX;
unsigned long flags4 = ~static_cast<unsigned long>(0);

Alle davon sind richtig und offensichtlicher für Ihre Programmierkollegen.

nd mit C++ 11: Wir können auto verwenden, um all dies noch einfacher zu machen:

auto flags1 = UINT_MAX;
auto flags2 = ~static_cast<unsigned int>(0);
auto flags3 = ULONG_MAX;
auto flags4 = ~static_cast<unsigned long>(0);

Ich halte richtig und offensichtlich besser als einfach richtig.

11
Adrian McCarthy

Das Konvertieren von -1 in einen beliebigen vorzeichenlosen Typ ist durch den Standard garantiert, um All-Einsen zu ergeben. Gebrauch von ~0U ist generell schlecht seit 0 hat den Typ unsigned int und werden nicht alle Bits eines größeren vorzeichenlosen Typs füllen, es sei denn, Sie schreiben explizit etwas wie ~0ULL. Auf vernünftigen Systemen ist ~0 sollte identisch sein mit -1, aber da der Standard die Darstellung von Einsen-Komplementen und Vorzeichen/Größen erlaubt, ist er streng genommen nicht portierbar.

Natürlich ist es immer in Ordnung zu schreiben 0xffffffff Wenn Sie wissen, dass Sie genau 32 Bit benötigen, -1 jedoch den Vorteil hat, dass es in jedem Kontext funktioniert, auch wenn Sie die Größe des Typs nicht kennen, z. B. Makros, die auf mehreren Typen funktionieren, oder wenn die Größe von Der Typ variiert je nach Implementierung. Wenn Sie den Typ kennen, sind die Limit-Makros UINT_MAX, ULONG_MAX, ULLONG_MAX, etc.

Persönlich benutze ich immer -1. Es funktioniert immer und man muss nicht darüber nachdenken.

10
R..

Ja. Wie in anderen Antworten erwähnt, -1 ist das tragbarste; Es ist jedoch nicht sehr semantisch und löst Compiler-Warnungen aus.

Probieren Sie diesen einfachen Helfer aus, um diese Probleme zu lösen:

static const struct All1s
{
    template<typename UnsignedType>
    inline operator UnsignedType(void) const
    {
        static_assert(std::is_unsigned<UnsignedType>::value, "This is designed only for unsigned types");
        return static_cast<UnsignedType>(-1);
    }
} ALL_BITS_TRUE;

Verwendung:

unsigned a = ALL_BITS_TRUE;
uint8_t  b = ALL_BITS_TRUE;
uint16_t c = ALL_BITS_TRUE;
uint32_t d = ALL_BITS_TRUE;
uint64_t e = ALL_BITS_TRUE;
5
Diamond Python

Solange Sie #include <limits.h> Als einen Ihrer Includes haben, sollten Sie nur verwenden

unsigned int flags = UINT_MAX;

Wenn Sie eine Menge wollen, können Sie verwenden

unsigned long flags = ULONG_MAX;

Bei diesen Werten werden garantiert alle Wertebits des Ergebnisses auf 1 gesetzt, unabhängig davon, wie Ganzzahlen mit Vorzeichen implementiert sind.

5
Michael Norrish

Ich würde das -1 Ding nicht machen. Es ist eher nicht intuitiv (zumindest für mich). Das Zuweisen signierter Daten zu einer nicht signierten Variablen scheint nur eine Verletzung der natürlichen Ordnung der Dinge zu sein.

In Ihrer Situation verwende ich immer 0xFFFF. (Verwenden Sie natürlich die richtige Anzahl von Fs für die variable Größe.)

[Übrigens sehe ich den -1-Trick nur sehr selten in echtem Code.]

Wenn Sie sich wirklich für die einzelnen Bits in einem Vairable interessieren, ist es ratsam, die uint8_t, uint16_t, uint32_t -Typen mit fester Breite zu verwenden .

3
myron-semack

Auf Intels IA-32-Prozessoren ist es in Ordnung, 0xFFFFFFFF in ein 64-Bit-Register zu schreiben und die erwarteten Ergebnisse zu erhalten. Dies liegt daran, dass IA32e (die 64-Bit-Erweiterung von IA32) nur 32-Bit-Direktzugriff unterstützt. In 64-Bit-Befehlen sind 32-Bit-Direktbefehle vorzeichenerweitert bis 64-Bit.

Folgendes ist illegal:

mov rax, 0ffffffffffffffffh

Das Folgende setzt 64 1s in RAX:

mov rax, 0ffffffffh

Der Vollständigkeit halber werden im unteren Teil von RAX (auch bekannt als EAX) 32 Einsen angegeben:

mov eax, 0ffffffffh

Tatsächlich sind Programme fehlgeschlagen, als ich 0xffffffff in eine 64-Bit-Variable schreiben wollte und stattdessen 0xffffffffffffff. In C wäre dies:

uint64_t x;
x = UINT64_C(0xffffffff)
printf("x is %"PRIx64"\n", x);

das Ergebnis ist:

x is 0xffffffffffffffff

Ich dachte, dies als Kommentar zu allen Antworten zu posten, die besagten, dass 0xFFFFFFFF 32 Bits voraussetzt, aber so viele Leute haben darauf geantwortet, dass ich es als separate Antwort hinzufügen würde.

2
Nathan Fellman

Obwohl 0xFFFF (Oder 0xFFFFFFFF Usw.) möglicherweise besser lesbar ist, kann dies die Portabilität von Code beeinträchtigen, der ansonsten portabel wäre. Betrachten Sie beispielsweise eine Bibliotheksroutine, um zu zählen, wie viele Elemente in einer Datenstruktur bestimmte Bits gesetzt haben (die genauen Bits werden vom Aufrufer angegeben). Die Routine ist möglicherweise völlig unabhängig von der Darstellung der Bits, muss jedoch eine Konstante "Alle Bits gesetzt" aufweisen. In einem solchen Fall ist -1 wesentlich besser als eine Hex-Konstante, da es mit jeder Bitgröße funktioniert.

Die andere Möglichkeit, wenn ein typedef -Wert für die Bitmaske verwendet wird, wäre ~ (bitMaskType) 0; Wenn es sich bei der Bitmaske nur um einen 16-Bit-Typ handelt, werden für diesen Ausdruck nur 16 Bit festgelegt (auch wenn 'int' andernfalls 32 Bit betragen würde). Da jedoch nur 16 Bit erforderlich sind, sollten die Dinge in Ordnung sein vorgesehen dass man tatsächlich den entsprechenden Typ im Typecast verwendet.

Übrigens haben Ausdrücke der Form longvar &= ~[hex_constant] Ein böses gotcha, wenn die Hex-Konstante zu groß ist, um in ein int zu passen, aber in ein unsigned int Passt. Wenn ein int 16 Bits hat, dann longvar &= ~0x4000; Oder longvar &= ~0x10000; löscht ein Bit von longvar, aber longvar &= ~0x8000; löscht Bit 15 und alle darüber liegenden Bits. Bei Werten, die in int passen, wird der Komplementoperator auf einen Typ int angewendet, das Ergebnis wird jedoch durch Vorzeichen auf long erweitert, wodurch die oberen Bits gesetzt werden. Bei Werten, die für unsigned int Zu groß sind, wird der Komplementoperator auf den Typ long angewendet. Werte, die zwischen diesen Größen liegen, wenden den Komplement-Operator jedoch auf Typ unsigned int An, der dann in Typ long ohne Vorzeichenerweiterung konvertiert wird.

2
supercat

Eine sehr klare Erklärung der Probleme finden Sie in der Antwort von litb.

Meiner Meinung nach gibt es in beiden Fällen streng genommen keine Garantien. Ich kenne keine Architektur, die keinen vorzeichenlosen Wert von 'eins weniger als zwei hoch der Anzahl der Bits' als alle gesetzten Bits darstellt, aber hier ist, was der Standard tatsächlich sagt (3.9.1/7 plus Anmerkung 44):

Die Darstellungen von Integraltypen müssen Werte unter Verwendung eines reinen Binärzahlensystems definieren. [Anmerkung 44:] Eine Positionsdarstellung für Ganzzahlen, die die Binärziffern 0 und 1 verwendet, in denen die durch aufeinanderfolgende Bits dargestellten Werte additiv sind, mit 1 beginnen und mit der aufeinanderfolgenden ganzzahligen Potenz von 2 multipliziert werden, außer vielleicht für das Bit mit die höchste Position.

Das lässt die Möglichkeit, dass eines der Bits überhaupt etwas ist.

2
James Hopkin

Praktisch: ja

Theoretisch: Nein.

-1 = 0xFFFFFFFF (oder welche Größe auch immer ein int auf Ihrer Plattform hat) gilt nur für die Zweierkomplementarithmetik. In der Praxis wird es funktionieren, aber es gibt ältere Maschinen (IBM-Mainframes usw.), auf denen Sie ein tatsächliches Vorzeichen anstelle einer Zweierkomplementdarstellung haben. Ihre vorgeschlagene ~ 0-Lösung sollte überall funktionieren.

1
Drew Hall

Wie bereits erwähnt, ist -1 die richtige Methode, um eine Ganzzahl zu erstellen, die mit allen auf 1 gesetzten Bits in einen vorzeichenlosen Typ konvertiert wird. Das Wichtigste in C++ ist jedoch, dass die richtigen Typen verwendet werden. Daher lautet die richtige Antwort auf Ihr Problem (einschließlich der Antwort auf die von Ihnen gestellte Frage) wie folgt:

std::bitset<32> const flags(-1);

Dies wird immer die exakte Anzahl von Bits enthalten, die Sie benötigen. Es konstruiert ein std::bitset mit allen Bits auf 1 aus den gleichen Gründen, die in anderen Antworten erwähnt wurden.

1
David Stone

Eine Möglichkeit, die Bedeutung etwas offensichtlicher zu machen und dennoch zu vermeiden, den Typ zu wiederholen:

const auto flags = static_cast<unsigned int>(-1);
0
FlorianH

Es ist sicher, da -1 immer alle verfügbaren Bits gesetzt hat, aber ich mag ~ 0 besser. -1 macht für einen unsigned int Einfach nicht viel Sinn. 0xFF ... ist nicht gut, da es von der Breite des Typs abhängt.

0
Zifre

Die Tatsache zu nutzen, dass das Zuweisen aller Bits zu einem für einen vorzeichenlosen Typ gleichbedeutend ist mit der Annahme des maximal möglichen Werts für den gegebenen Typ.
und Ausdehnung des Geltungsbereichs der Frage auf alle vorzeichenlosen Ganzzahltypen:

Zuweisen von -1 funktioniert für jeden vorzeichenlosen Ganzzahltyp (vorzeichenloses int, uint8_t, uint16_t usw.) für C und C++ .

Alternativ können Sie für C++ entweder:

  1. Füge <limits> Ein und benutze std::numeric_limits< your_type >::max()
  2. Schreiben Sie eine benutzerdefinierte Template-Funktion (Dies würde auch eine Überprüfung der Integrität ermöglichen, d. H. Wenn der Zieltyp wirklich ein nicht signierter Typ ist)

Der Zweck könnte mehr Klarheit sein, da die Zuweisung von -1 Immer einen erklärenden Kommentar erfordert.

0
Antonio

Ich sage:

int x;
memset(&x, 0xFF, sizeof(int));

So erhalten Sie immer das gewünschte Ergebnis.

0
Alex