Was sind die Best Practices für nicht signierte Ints?

43

Ich verwende überall nicht signierte Ints und bin mir nicht sicher, ob ich sollte. Dies kann von Datenbankprimärschlüssel-ID-Spalten bis zu Zählern usw. reichen. Wenn eine Zahl niemals negativ sein sollte, verwende ich immer ein vorzeichenloses int.

Ich bemerke jedoch aus dem Code eines anderen, dass dies anscheinend niemand anderes tut. Gibt es etwas Entscheidendes, das ich übersehen habe?

Edit: Seit dieser Frage ist mir auch aufgefallen, dass es in C üblich ist, negative Werte für Fehler zurückzugeben, anstatt wie in C ++ Ausnahmen auszulösen.

wting
quelle
26
Achten Sie einfach auf for(unsigned int n = 10; n >= 0; n --)(Endlosschleifen)
Chris Burt-Brown
3
In C und C ++ haben vorzeichenlose Ints ein genau definiertes Überlaufverhalten (Modulo 2 ^ n). Signierte Ints nicht. Optimierer nutzen dieses undefinierte Überlaufverhalten zunehmend aus, was in einigen Fällen zu überraschenden Ergebnissen führt.
Steve314
2
Gute Frage! Auch ich war einmal versucht, Uints zu verwenden, um die Reichweite einzuschränken, stellte jedoch fest, dass das Risiko / die Unannehmlichkeiten den Nutzen / die Bequemlichkeit überwogen. Die meisten Bibliotheken akzeptieren, wie Sie sagten, reguläre Ints, wo ein Uint tun würde. Das erschwert die Arbeit, wirft aber auch die Frage auf: Lohnt es sich? In der Praxis (vorausgesetzt, Sie gehen Dinge nicht dumm an), werden Sie selten einen Wert von -218 haben, bei dem ein positiver Wert erwartet wird. Das muss von irgendwoher gekommen sein, oder? und Sie können seinen Ursprung verfolgen. Kommt selten vor. Verwenden Sie Behauptungen, Ausnahmen und Codeverträge, um Sie zu unterstützen.
Job
@ William Ting: Wenn es sich nur um C / C ++ handelt, sollten Sie Ihrer Frage die entsprechenden Tags hinzufügen.
CesarGon
2
@ Chris: Wie bedeutend ist das Endlosschleifenproblem in der Realität? Ich meine, wenn es seinen Weg in die Veröffentlichung findet, dann wurde der Code offensichtlich nicht getestet. Selbst wenn Sie beim ersten Auftreten dieses Fehlers einige Stunden für das Debuggen benötigen, sollten Sie beim zweiten Auftreten wissen, wonach Sie zuerst suchen müssen, wenn der Code nicht aufhört, sich zu wiederholen.
Sichern Sie sich den

Antworten:

28

Gibt es etwas Entscheidendes, das ich übersehen habe?

Wenn bei Berechnungen sowohl vorzeichenbehaftete als auch nicht vorzeichenbehaftete Typen sowie unterschiedliche Größen berücksichtigt werden, können die Regeln für die Typheraufstufung komplex sein und zu unerwartetem Verhalten führen .

Ich glaube, dies ist der Hauptgrund, warum Java Int-Typen ohne Vorzeichen ausgelassen hat.

Michael Borgwardt
quelle
3
Eine andere Lösung wäre, von Ihnen zu verlangen, dass Sie Ihre Nummern nach Bedarf manuell eingeben. Dies ist, was Go zu tun scheint (ich habe aber nur ein bisschen damit herumgespielt), und ich mag es mehr als Javas Herangehensweise.
Tikhon Jelvis
2
Das war ein guter Grund für Java, keinen 64-Bit-Typ ohne Vorzeichen und vielleicht auch keinen 32-Bit-Typ ohne Vorzeichen zu verwenden. Eine solche Operation sollte einfach ein 64-Bit-Ergebnis mit Vorzeichen ergeben. Unsignierte Typen, die kleiner sind als die, intdie eine solche Schwierigkeit darstellen würden, stellen jedoch keine solche Schwierigkeit dar (da Berechnungen zu fördern sind int); Ich kann nichts Gutes über das Fehlen eines Typs ohne Vorzeichen sagen.
Supercat
17

Ich denke, dass Michael einen gültigen Punkt hat, aber IMO der Grund, warum jeder int die ganze Zeit (besonders in for (int i = 0; i < max, i++) verwendet, ist, dass wir es so gelernt haben. Wenn jedes einzelne Beispiel in einem Buch über das Erlernen des Programmierensint in einer forSchleife verwendet wird, werden sehr wenige diese Praxis jemals in Frage stellen.

Der andere Grund ist, dass int25% kürzer sind als uint, und wir sind alle faul ... ;-)

Treb
quelle
2
Ich stimme der pädagogischen Frage zu. Die meisten Leute scheinen nie in Frage zu stellen, was sie lesen: Wenn es in einem Buch steht, kann es nicht falsch sein, oder?
Matthieu M.
1
Das ist vermutlich auch der Grund, warum jeder ++beim Inkrementieren Postfix verwendet , obwohl sein spezielles Verhalten selten benötigt wird und sogar zu sinnlosem Umblättern von Kopien führen kann, wenn der Schleifenindex ein Iterator oder ein anderer nicht grundlegender Typ ist (oder der Compiler wirklich dicht ist). .
Underscore_d
Mach einfach nichts wie "für (uint i = 10; i> = 0; --i)". Die Verwendung von nur ints für Schleifenvariablen vermeidet diese Möglichkeit.
David Thornley
8

Das Mischen von signierten und nicht signierten Typen kann Sie in eine Welt der Schmerzen versetzen. Und Sie können nicht alle vorzeichenlosen Typen verwenden, da Sie auf Dinge stoßen, die entweder einen gültigen Bereich haben, der negative Zahlen enthält, oder einen Wert benötigen, um einen Fehler anzuzeigen, und -1 ist am natürlichsten. Das Nettoergebnis ist also, dass viele Programmierer alle vorzeichenbehafteten Ganzzahltypen verwenden.

David Schwartz
quelle
1
Vielleicht ist es besser, gültige Werte nicht mit Fehleranzeigen in derselben Variablen zu mischen und dafür separate Variablen zu verwenden. Zugegeben, die C-Standardbibliothek ist hier kein gutes Beispiel.
Sichern Sie sich den
7

Bei mir dreht sich viel um Kommunikation. Indem Sie explizit ein vorzeichenloses int verwenden, teilen Sie mir mit, dass vorzeichenbehaftete Werte keine gültigen Werte sind. Auf diese Weise kann ich beim Lesen des Codes zusätzlich zum Variablennamen einige Informationen hinzufügen. Im Idealfall würde mir ein nicht anonymer Typ mehr sagen, aber er gibt mir mehr Informationen, als wenn Sie überall Ints verwendet hätten.

Leider sind sich nicht alle bewusst, was ihr Code kommuniziert, und das ist wahrscheinlich der Grund, warum Sie überall Ints sehen, obwohl die Werte zumindest nicht mit Vorzeichen versehen sind.

Daramarak
quelle
4
Möglicherweise möchte ich meine Werte jedoch für einen Monat auf 1 bis 12 beschränken. Benutze ich einen anderen Typ dafür? Was ist mit einem Monat? Einige Sprachen erlauben es tatsächlich, solche Werte einzuschränken. Andere wie .Net / C # bieten Codeverträge an. Sicher, nicht negative ganze Zahlen kommen ziemlich häufig vor, aber die meisten Sprachen, die diesen Typ unterstützen, unterstützen keine weiteren Einschränkungen. Sollte man also eine Mischung aus uints und Fehlerprüfung verwenden oder einfach alles durch Fehlerprüfung tun? Die meisten Bibliotheken fragen nicht nach dem Uint, wo es sinnvoll wäre, einen zu verwenden. Daher kann die Verwendung eines solchen Codes unpraktisch sein.
Job
@Job Ich würde sagen, Sie sollten eine Art Compiler / Interpreter verwenden, der Ihre Monate einschränkt. Es gibt Ihnen vielleicht ein wenig Zeit zum Einrichten, aber für die Zukunft haben Sie eine erzwungene Einschränkung, die Fehler vermeidet und viel klarer kommuniziert, was Sie erwarten. Die Vermeidung von Fehlern und die Erleichterung der Kommunikation sind bei der Implementierung wichtiger als Unannehmlichkeiten.
Daramarak
1
"Vielleicht möchte ich meine Werte für einen Monat auf 1 bis 12 beschränken." Wenn Sie eine endliche Menge von Werten wie Monate haben, sollten Sie einen Aufzählungstyp verwenden, keine rohen ganzen Zahlen.
Josh Caswell
6

Ich verwende unsigned intin C ++ hauptsächlich für Array-Indizes und für jeden Zähler, der mit 0 beginnt. Ich denke, es ist gut, explizit zu sagen, dass diese Variable nicht negativ sein kann.

quant_dev
quelle
14
Sie sollten wahrscheinlich size_t dafür in c ++ verwenden
JohnB
2
Ich weiß, ich kann mich einfach nicht darum kümmern.
quant_dev
3

Dies sollte beachtet werden, wenn es sich um eine Ganzzahl handelt, die sich tatsächlich den Grenzwerten eines signierten Int. Nähert oder diese überschreitet. Da das positive Maximum einer 32-Bit-Ganzzahl 2.147.483.647 ist, sollten Sie ein vorzeichenloses int verwenden, wenn Sie wissen, dass es a) niemals negativ ist und b) möglicherweise 2.147.483.648 erreicht. In den meisten Fällen, einschließlich Datenbankschlüsseln und Zählern, werde ich mich diesen Zahlen nicht einmal nähern, sodass ich mich nicht darum kümmern muss, ob das Vorzeichenbit für einen numerischen Wert oder zum Anzeigen des Vorzeichens verwendet wird.

Ich würde sagen: Verwenden Sie int, es sei denn, Sie wissen, dass Sie ein nicht signiertes int benötigen.

Joel Etherton
quelle
2
Wenn Sie mit Werten arbeiten, die die Maximalwerte erreichen können, sollten Sie die Operationen unabhängig vom Vorzeichen auf Ganzzahlüberläufe überprüfen. Diese Überprüfungen sind in der Regel für nicht signierte Typen einfacher, da die meisten Operationen gut definierte Ergebnisse ohne undefiniertes und implementierungsdefiniertes Verhalten liefern.
Sichern Sie sich den
3

Es ist ein Kompromiss zwischen Einfachheit und Zuverlässigkeit. Je mehr Fehler beim Kompilieren gefunden werden, desto zuverlässiger ist die Software. Unterschiedliche Personen und Organisationen befassen sich in diesem Spektrum mit unterschiedlichen Aspekten.

Wenn Sie jemals eine hochzuverlässige Programmierung in Ada durchführen, verwenden Sie sogar unterschiedliche Typen für Variablen wie Entfernung in Fuß oder Entfernung in Metern. Der Compiler kennzeichnet diese, wenn Sie versehentlich eine andere zuweisen. Das ist perfekt zum Programmieren einer Lenkwaffe, aber übertrieben (Wortspiel beabsichtigt), wenn Sie ein Webformular validieren. In beiden Fällen muss nichts falsch sein, solange es den Anforderungen entspricht.

Karl Bielefeldt
quelle
2

Ich bin geneigt, Joel Ethertons Argumentation zuzustimmen, komme aber zu dem gegenteiligen Schluss. So wie ich es sehe, auch wenn Sie wissen , dass die Zahlen wahrscheinlich nicht immer die Grenzen eines signierten Art zu nähern, wenn Sie wissen , dass negative Zahlen nicht passieren wird , dann gibt es sehr wenig Grund , die signierte Variante eines Typs zu verwenden.

Aus dem gleichen Grund habe ich in einigen ausgewählten Fällen BIGINT(64-Bit-Ganzzahl) und nicht INTEGER(32-Bit-Ganzzahl) in SQL Server-Tabellen verwendet. Die Wahrscheinlichkeit, dass die Daten innerhalb eines angemessenen Zeitraums die 32-Bit-Grenze erreichen, ist gering. Sollte dies jedoch der Fall sein, können die Konsequenzen in einigen Situationen verheerend sein. Achten Sie nur darauf, dass Sie die Typen zwischen den Sprachen richtig zuordnen, sonst werden Sie in der nächsten Zeit eine interessante Verrücktheit erleben ...

Das heißt, für einige Dinge, wie z. B. Werte von Datenbankprimärschlüsseln, die signiert oder nicht signiert sind, spielt es keine Rolle, denn es sei denn, Sie reparieren fehlerhafte Daten oder ähnliches manuell, so dass Sie sich nie direkt mit dem Wert befassen. Es ist ein Identifikator, nichts weiter. In diesen Fällen ist die Konsistenz wahrscheinlich wichtiger als die genaue Wahl der Signatur. Andernfalls haben Sie einige Fremdschlüsselspalten, die signiert sind, und andere, die nicht signiert sind, ohne erkennbares Muster - oder wieder diese interessante Verrücktheit.

ein CVn
quelle
Wenn Sie mit Daten arbeiten, die aus einem SAP-System extrahiert wurden, empfehle ich dringend BIGINT für ID-Felder (wie CustomerNumber, ArticleNumber usw.). Solange niemand verwendet alphanumerische Zeichenfolgen als IDs, dh ... seufzen
Treb
1

Ich würde empfehlen, dass man außerhalb platzbeschränkter Datenspeicher- und Datenaustauschkontexte generell signierte Typen verwenden sollte. In den meisten Fällen, in denen eine 32-Bit-Ganzzahl mit Vorzeichen zu klein wäre, aber ein 32-Bit-Wert ohne Vorzeichen für heute ausreicht, wird es nicht lange dauern, bis der 32-Bit-Wert ohne Vorzeichen auch nicht groß genug ist.

Die primären Zeiten, in denen man vorzeichenlose Typen verwenden sollte, sind, wenn man entweder mehrere Werte zu einem größeren zusammensetzt (z. B. vier Bytes in eine 32-Bit-Zahl umwandelt) oder größere Werte in kleinere zerlegt (z. B. eine 32-Bit-Zahl als vier Bytes speichert) ) oder wenn man eine Menge hat, von der erwartet wird, dass sie periodisch "überschlägt", und man sich damit befassen muss (man denke an einen Haushaltszähler; die meisten von ihnen haben genügend Ziffern, um sicherzustellen, dass sie möglicherweise nicht zwischen den Ablesungen überschlagen wenn sie dreimal im Jahr gelesen werden, aber nicht ausreichen, um sicherzustellen, dass sie sich nicht innerhalb der Nutzungsdauer des Messgeräts überschlagen). Unsignierte Typen haben oft genug Verrücktheit, dass sie nur in Fällen verwendet werden sollten, in denen ihre Semantik notwendig ist.

Superkatze
quelle
1
"Ich würde empfehlen, [...] generell signierte Typen zu verwenden." Hm, Sie haben vergessen, die Vorteile von vorzeichenbehafteten Typen zu erwähnen, und haben nur angegeben, wann vorzeichenlose Typen zu verwenden sind. "Verrücktheit" ? Während die meisten Operationen ohne Vorzeichen ein genau definiertes Verhalten und Ergebnisse aufweisen, geben Sie bei Verwendung von vorzeichenbehafteten Typen (Überlauf, Bitverschiebung, ...) ein undefiniertes und implementierungsdefiniertes Verhalten ein. Sie haben hier eine seltsame Definition von "Verrücktheit".
Sichern Sie sich den
1
@Secure: Die "Verrücktheit", auf die ich mich beziehe, hat mit der Semantik von Vergleichsoperatoren zu tun, insbesondere bei Operationen, die gemischte vorzeichenbehaftete und vorzeichenlose Typen beinhalten. Sie haben Recht, dass das Verhalten von vorzeichenbehafteten Typen undefiniert ist, wenn Werte verwendet werden, die groß genug sind, um einen Überlauf zu verursachen. Das Verhalten von vorzeichenlosen Typen kann jedoch selbst bei relativ kleinen Zahlen überraschend sein. Zum Beispiel ist (-3) + (1u) größer als -1. Einige normale mathematisch-assoziative Beziehungen, die für Zahlen gelten, gelten auch nicht für vorzeichenlose. Zum Beispiel impliziert (ab)> c nicht (ac)> b.
Supercat
1
@Secure: Während es stimmt, dass man sich auch bei "großen" vorzeichenbehafteten Zahlen nicht immer auf ein solches assoziatives Verhalten verlassen kann, funktioniert das Verhalten bei Zahlen, die im Verhältnis zur Domäne der vorzeichenbehafteten ganzen Zahlen "klein" sind, wie erwartet. Im Gegensatz dazu ist die oben erwähnte Nichtassoziation bei vorzeichenlosen Werten "2 3 1" problematisch. Im Übrigen kann die Tatsache, dass signierte Verhaltensweisen ein undefiniertes Verhalten aufweisen, wenn sie außerhalb der Grenzen verwendet werden, auf einigen Plattformen eine verbesserte Codegenerierung ermöglichen, wenn Werte verwendet werden, die kleiner als die native Wortgröße sind.
Supercat
1
Wären diese Kommentare in Ihrer Antwort an erster Stelle gewesen, anstelle einer Empfehlung und "Namensnennung" ohne Angabe von Gründen, hätte ich sie nicht kommentiert. ;) Obwohl ich hier immer noch nicht mit "Verrücktheit" einverstanden bin, ist es einfach die Definition des Typs. Verwenden Sie das richtige Werkzeug für die jeweilige Aufgabe und kennen Sie das Werkzeug natürlich. Vorzeichenlose Typen sind das falsche Werkzeug, wenn Sie +/- Relationen benötigen. Es gibt einen Grund, warum size_tnicht signiert und ptrdiff_tsigniert ist.
Sichern Sie sich den
1
@Secure: Wenn man eine Folge von Bits darstellen möchte, sind vorzeichenlose Typen großartig. Ich denke, wir sind uns da einig. Und auf einigen kleinen Mikros können vorzeichenlose Typen für numerische Mengen effizienter sein. Sie sind auch in Fällen nützlich, in denen Deltas numerische Größen darstellen, die tatsächlichen Werte jedoch nicht (z. B. TCP-Sequenznummern). Auf der anderen Seite muss man sich jedes Mal, wenn man vorzeichenlose Werte subtrahiert, über Eckfälle Gedanken machen, selbst wenn die Zahlen klein sind; Solche Berechnungen mit vorzeichenbehafteten Werten stellen nur Eckfälle dar, wenn die Zahlen groß sind.
Supercat
1

Ich verwende unsignierte Ints, um meinen Code und seine Absichten klarer zu machen. Zum Schutz vor unerwarteten impliziten Konvertierungen beim Rechnen mit vorzeichenbehafteten und vorzeichenlosen Typen verwende ich eine vorzeichenlose Kurzform (normalerweise 2 Byte) für meine vorzeichenlosen Variablen. Dies ist aus mehreren Gründen effektiv:

  • Wenn Sie mit Ihren vorzeichenlosen Kurzvariablen und Literalen (die vom Typ int sind) oder Variablen vom Typ int arithmetisch arbeiten, wird sichergestellt, dass die vorzeichenlose Variable vor der Auswertung des Ausdrucks immer zu einem int hochgestuft wird, da int immer einen höheren Rang als short hat . Dies vermeidet unerwartetes Verhalten beim Rechnen mit vorzeichenbehafteten und vorzeichenlosen Typen, vorausgesetzt, das Ergebnis des Ausdrucks passt natürlich in ein vorzeichenbehaftetes int.
  • In den meisten Fällen überschreiten die von Ihnen verwendeten vorzeichenlosen Variablen nicht den Maximalwert eines vorzeichenlosen 2-Byte-Kurzschlusses (65.535).

Das allgemeine Prinzip ist, dass der Typ Ihrer vorzeichenlosen Variablen einen niedrigeren Rang haben sollte als der Typ der vorzeichenbehafteten Variablen, um die Heraufstufung zum vorzeichenbehafteten Typ sicherzustellen. Dann haben Sie kein unerwartetes Überlaufverhalten. Natürlich können Sie dies nicht immer sicherstellen, aber (meistens) ist es möglich, dies sicherzustellen.

Zum Beispiel hatte ich kürzlich eine for-Schleife, die ungefähr so ​​aussah:

const unsigned short cuint = 5;
for(unsigned short i=0; i<10; ++i)
{
    if((i-2)%cuint == 0)
    {
       //Do something
    }
}

Das Literal '2' ist vom Typ int. Wenn i ein vorzeichenloses int anstelle eines vorzeichenlosen short wäre, würde in dem Unterausdruck (i-2) 2 zu einem vorzeichenlosen int heraufgestuft (da vorzeichenloses int eine höhere Priorität als vorzeichenloses int hat). Wenn i = 0 ist, ist der Unterausdruck gleich (0u-2u) = ein massiver Wert aufgrund eines Überlaufs. Dieselbe Idee mit i = 1. Da i jedoch ein Short ohne Vorzeichen ist, wird es auf den gleichen Typ wie Literal '2' hochgestuft, das mit int signiert ist, und alles funktioniert einwandfrei.

Für zusätzliche Sicherheit: In dem seltenen Fall, dass die Architektur, auf der Sie implementieren, int auf 2 Byte erhöht, werden möglicherweise beide Operanden im arithmetischen Ausdruck in unsigned int hochgestuft, wenn die unsigned short-Variable nicht passt in das vorzeichenbehaftete 2-Byte-int, dessen letzterer einen Maximalwert von 32.767 <65.535 hat. ( Weitere Informationen finden Sie unter https://stackoverflow.com/questions/17832815/c-implicit-conversion-signed-unsigned .) Um sich davor zu schützen, können Sie Ihrem Programm einfach einen static_assert wie folgt hinzufügen:

static_assert(sizeof(int) == 4, "int must be 4 bytes");

und es wird nicht auf Architekturen kompiliert, bei denen int 2 Bytes beträgt.

AdmiralAdama
quelle