Ich habe eine sehr einfache Frage, die mich lange Zeit verblüfft. Ich habe es mit Netzwerken und Datenbanken zu tun, daher handelt es sich bei vielen Daten, mit denen ich zu tun habe, um 32-Bit- und 64-Bit-Zähler (ohne Vorzeichen), 32-Bit- und 64-Bit-Identifikations-IDs (auch ohne aussagekräftige Zuordnung für Vorzeichen). Ich beschäftige mich praktisch nie mit realen Wörtern, die als negative Zahl ausgedrückt werden könnten.
Ich und meine Mitarbeiter verwenden routinemäßig vorzeichenlose Typen wie uint32_t
und uint64_t
für diese Zwecke und da dies so häufig vorkommt, verwenden wir sie auch für Array-Indizes und andere allgemeine Ganzzahlverwendungen.
Gleichzeitig lese ich verschiedene Codierungsleitfäden (z. B. Google), die von der Verwendung von Ganzzahltypen ohne Vorzeichen abraten, und meines Wissens haben weder Java noch Scala Ganzzahltypen ohne Vorzeichen.
Daher konnte ich nicht herausfinden, was das Richtige ist: Es wäre sehr unpraktisch, signierte Werte in unserer Umgebung zu verwenden. Gleichzeitig sollten Sie in Codierungsanleitungen darauf bestehen, genau dies zu tun.
quelle
Antworten:
Es gibt zwei Denkschulen, und keine wird jemals zustimmen.
Das erste Argument besagt, dass es einige Konzepte gibt, die von Natur aus nicht mit Vorzeichen versehen sind, wie z. B. Array-Indizes. Es ist nicht sinnvoll, vorzeichenbehaftete Zahlen zu verwenden, da dies zu Fehlern führen kann. Es kann auch unnötige Beschränkungen für Dinge festlegen - ein Array, das vorzeichenbehaftete 32-Bit-Indizes verwendet, kann nur auf 2 Milliarden Einträge zugreifen, während das Umschalten auf vorzeichenlose 32-Bit-Nummern 4 Milliarden Einträge zulässt.
Das zweite Argument besagt, dass in jedem Programm, das vorzeichenlose Zahlen verwendet, früher oder später gemischte vorzeichenlose Arithmetik ausgeführt wird. Dies kann zu seltsamen und unerwarteten Ergebnissen führen: Wenn Sie einen großen Wert ohne Vorzeichen in "Vorzeichen" umwandeln, erhalten Sie eine negative Zahl, und wenn Sie einen negativen Wert in "Vorzeichen" umwandeln, erhalten Sie eine große positive Zahl. Dies kann eine große Fehlerquelle sein.
quelle
int
ist kürzer zu tippen :)int
ist dies in 99,99% der Fälle mehr als genug für Array-Indizes. Die vorzeichenbehafteten - vorzeichenlosen arithmetischen Probleme sind weitaus häufiger und haben daher Vorrang in Bezug auf das, was zu vermeiden ist. Ja, Compiler warnen Sie davor, aber wie viele Warnungen erhalten Sie, wenn Sie ein umfangreiches Projekt kompilieren? Das Ignorieren von Warnungen ist gefährlich und eine schlechte Praxis, aber in der realen Welt ...size_t
, es sei denn , ein Spezialfall ist aus gutem grund anders.Zuallererst ist die Google C ++ - Codierungsrichtlinie nicht sehr gut zu befolgen: Sie meidet Dinge wie Ausnahmen, Boost usw., die Grundvoraussetzungen für modernes C ++ sind. Zweitens bedeutet nur, dass eine bestimmte Richtlinie für Unternehmen X funktioniert, nicht, dass sie für Sie geeignet ist. Ich würde weiterhin unsignierte Typen verwenden, da Sie ein gutes Bedürfnis nach ihnen haben.
Eine
int
gute Faustregel für C ++ lautet: Bevorzugen Sie, es sei denn, Sie haben einen guten Grund, etwas anderes zu verwenden.quelle
return false
wenn diese Invariante nicht eingerichtet ist. Sie können also entweder Dinge trennen und Init-Funktionen für Ihre Objekte verwenden, oder Sie können ein werfenstd::runtime_error
, das Abwickeln des Stapels zulassen und alle RAII-Objekte automatisch bereinigen, und Sie als Entwickler können die Ausnahme behandeln, für die dies praktisch ist Sie dazu.nullptr
? Ein "Standard" -Objekt zurückgeben (was auch immer das bedeuten mag)? Sie haben nichts gelöst - Sie haben das Problem nur unter einem Teppich versteckt und hoffen, dass niemand es herausfindet.signal(6)
? Wenn Sie eine Ausnahme verwenden, können die 50% der Entwickler, die wissen, wie sie damit umgehen sollen, guten Code schreiben, und der Rest kann von ihren Kollegen getragen werden.In den anderen Antworten fehlen Beispiele aus der Praxis, daher möchte ich eines hinzufügen. Einer der Gründe, warum ich (persönlich) versuche, nicht signierte Typen zu vermeiden.
Verwenden Sie den Standard size_t als Array-Index:
Ok, ganz normal. Bedenken Sie dann, dass wir uns aus irgendeinem Grund entschieden haben, die Richtung der Schleife zu ändern:
Und jetzt klappt es nicht. Wenn wir es
int
als Iterator verwenden würden, gäbe es kein Problem. Ich habe solche Fehler in den letzten zwei Jahren zweimal gesehen. Einmal passierte es in der Produktion und war schwer zu debuggen.Ein weiterer Grund für mich sind nervige Warnungen, bei denen man jedes Mal so etwas schreibt :
Das sind Kleinigkeiten, aber sie summieren sich. Ich denke, der Code ist sauberer, wenn überall nur Ganzzahlen mit Vorzeichen verwendet werden.
Edit: Sicher, die Beispiele sehen dumm aus, aber ich habe gesehen, wie Leute diesen Fehler gemacht haben. Wenn es so einfach ist, es zu vermeiden, warum nicht?
Wenn ich den folgenden Code mit VS2015 oder GCC kompiliere, werden keine Warnungen mit Standardwarnungseinstellungen angezeigt (auch mit -Wall für GCC). Sie müssen nach -Wextra fragen, um eine Warnung in GCC zu erhalten. Dies ist einer der Gründe, warum Sie immer mit Wall und Wextra kompilieren sollten (und einen statischen Analysator verwenden sollten), aber in vielen realen Projekten tun die Leute das nicht.
quelle
for (size_t i = n - 1; i < n; --i)
, damit es richtig funktioniert.size_t
for (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
int
? :)int
groß genug sind, um alle gültigen Werte von aufzunehmensize_t
. Insbesondereint
können Nummern nur bis zu 2 ^ 15-1 zugelassen werden, und dies gilt normalerweise für Systeme mit einer Speicherzuweisungsgrenze von 2 ^ 16 (oder in bestimmten Fällen sogar höher).long
Möglicherweise ist dies eine sicherere Wette, es ist jedoch immer noch nicht garantiert , dass sie funktioniert. Nursize_t
funktioniert garantiert auf allen Plattformen und in allen Fällen.Das Problem hierbei ist, dass Sie die Schleife auf eine ungewöhnliche Art und Weise geschrieben haben, was zu einem fehlerhaften Verhalten führte. Die Konstruktion der Schleife ist so, als würde sie Anfängern für vorzeichenbehaftete Typen beigebracht (was in Ordnung und korrekt ist), passt aber einfach nicht für vorzeichenlose Werte. Dies kann jedoch nicht als Gegenargument gegen die Verwendung von vorzeichenlosen Typen dienen. Hier geht es einfach darum, die richtige Schleife zu finden. Und dies kann leicht behoben werden, um für nicht signierte Typen zuverlässig zu funktionieren:
Diese Änderung kehrt einfach die Reihenfolge des Vergleichs und der Dekrementierungsoperation um und ist meiner Meinung nach der effektivste, störungsfreieste, sauberste und kürzeste Weg, um vorzeichenlose Zähler in Rückwärtsschleifen zu behandeln. Sie würden genau dasselbe (intuitiv) tun, wenn Sie eine while-Schleife verwenden:
Es kann kein Unterlauf auftreten, der Fall eines leeren Behälters wird implizit abgedeckt, wie bei der bekannten Variante für die vorzeichenbehaftete Zählerschleife, und der Körper der Schleife kann im Vergleich zu einem vorzeichenbehafteten Zähler oder einer Vorwärtsschleife unverändert bleiben. Man muss sich nur an das zunächst etwas seltsam aussehende Loop-Konstrukt gewöhnen. Aber nachdem Sie das ein Dutzend Mal gesehen haben, ist nichts mehr unverständlich.
Ich hätte das Glück, wenn Anfängerkurse nicht nur die richtige Schleife für signierte, sondern auch für nicht signierte Typen zeigen würden. Dies würde ein paar Fehler vermeiden, die IMHO den unwissenden Entwicklern vorgeworfen werden sollten, anstatt dem nicht signierten Typ die Schuld zu geben.
HTH
quelle
Ganzzahlen ohne Vorzeichen gibt es nicht ohne Grund.
Denken Sie beispielsweise daran, Daten als einzelne Bytes zu übergeben, z. B. in einem Netzwerkpaket oder in einem Dateipuffer. Es kann gelegentlich vorkommen, dass Sie auf Bestien wie 24-Bit-Ganzzahlen stoßen. Einfache Bitverschiebung von drei 8-Bit-Ganzzahlen ohne Vorzeichen, bei 8-Bit-Ganzzahlen mit Vorzeichen nicht so einfach.
Oder denken Sie über Algorithmen nach, die Nachschlagetabellen für Zeichen verwenden. Wenn ein Zeichen eine 8-Bit-Ganzzahl ohne Vorzeichen ist, können Sie eine Nachschlagetabelle nach einem Zeichenwert indizieren. Was tun Sie jedoch, wenn die Programmiersprache keine Ganzzahlen ohne Vorzeichen unterstützt? Sie hätten negative Indizes zu einem Array. Nun, ich denke, Sie könnten so etwas gebrauchen,
charval + 128
aber das ist einfach hässlich.Tatsächlich verwenden viele Dateiformate Ganzzahlen ohne Vorzeichen. Wenn die Programmiersprache der Anwendung Ganzzahlen ohne Vorzeichen nicht unterstützt, kann dies ein Problem sein.
Dann betrachten Sie die TCP-Sequenznummern. Wenn Sie einen TCP-Verarbeitungscode schreiben, sollten Sie auf jeden Fall Ganzzahlen ohne Vorzeichen verwenden.
Manchmal ist Effizienz so wichtig, dass Sie wirklich ein bisschen vorzeichenlose Ganzzahlen benötigen. Betrachten Sie beispielsweise IoT-Geräte, die in Millionen ausgeliefert werden. Viele Programmierressourcen können dann gerechtfertigt für Mikrooptimierungen ausgegeben werden.
Ich würde argumentieren, dass die Rechtfertigung, vorzeichenlose Integer-Typen (Mischzeichen-Arithmetik, Mischzeichen-Vergleiche) zu vermeiden, von einem Compiler mit richtigen Warnungen überwunden werden kann. Solche Warnungen sind normalerweise nicht standardmäßig aktiviert, sondern werden z. B.
-Wextra
oder separat-Wsign-compare
(automatisch aktiviert in C von-Wextra
, obwohl ich glaube, dass es in C ++ nicht automatisch aktiviert ist) und angezeigt-Wsign-conversion
.Verwenden Sie im Zweifelsfall dennoch einen signierten Typ. Oft ist es eine Wahl, die gut funktioniert. Und aktivieren Sie diese Compiler-Warnungen!
quelle
Es gibt viele Fälle, in denen Ganzzahlen eigentlich keine Zahlen darstellen, aber zum Beispiel eine Bitmaske, eine ID usw. Grundsätzlich hat das Hinzufügen von 1 zu einer Ganzzahl kein aussagekräftiges Ergebnis. In diesen Fällen verwenden Sie unsigned.
Es gibt viele Fälle, in denen Sie mit ganzen Zahlen rechnen. Verwenden Sie in diesen Fällen vorzeichenbehaftete Ganzzahlen, um Fehlverhalten um Null herum zu vermeiden. Sehen Sie sich viele Beispiele für Schleifen an, bei denen das Ausführen einer Schleife bis auf Null entweder sehr unintuitiven Code verwendet oder aufgrund der Verwendung von Zahlen ohne Vorzeichen unterbrochen wird. Es gibt das Argument "aber Indizes sind nie negativ" - sicher, aber Unterschiede von Indizes zum Beispiel sind negativ.
In dem sehr seltenen Fall, dass Indizes 2 ^ 31, aber nicht 2 ^ 32 überschreiten, verwenden Sie keine vorzeichenlosen Ganzzahlen, sondern 64-Bit-Ganzzahlen.
Zum Schluss noch eine nette Falle: In einer Schleife "für (i = 0; i <n; ++ i) a [i] ..." kann der Compiler nicht optimieren, wenn i 32 Bit vorzeichenlos ist und der Speicher 32 Bit-Adressen überschreitet den Zugriff auf a [i] durch Inkrementieren eines Zeigers, da bei i = 2 ^ 32 - 1 ein Umlauf erfolgt. Auch wenn n nie so groß wird. Die Verwendung von Ganzzahlen mit Vorzeichen vermeidet dies.
quelle
Endlich habe ich hier eine wirklich gute Antwort gefunden: "Secure Programming Cookbook" von J.Viega und M.Messier ( http://shop.oreilly.com/product/9780596003944.do )
Sicherheitsprobleme mit signierten Ganzzahlen:
Es gibt Probleme mit signierten <-> nicht signierten Conversions, daher ist es nicht ratsam, mix zu verwenden.
quelle