In C ++ wird size_t
(oder genauer gesagt, T::size_type
was "normalerweise" ist size_t
, dh ein unsigned
Typ) als Rückgabewert für size()
, das Argument für operator[]
usw. verwendet (siehe std::vector
et al.).
Andererseits verwenden .NET-Sprachen int
(und optional long
) für denselben Zweck. Tatsächlich sind CLS-kompatible Sprachen nicht erforderlich, um nicht signierte Typen zu unterstützen .
Angesichts der Tatsache, dass .NET neuer als C ++ ist, gibt es Hinweise darauf, dass es möglicherweise Probleme bei der Verwendung gibt, unsigned int
auch für Dinge, die "unmöglich" negativ sein können, wie z. B. ein Array-Index oder eine Array-Länge. Ist der C ++ - Ansatz "historisches Artefakt" für die Abwärtskompatibilität? Oder gibt es echte und signifikante Designkompromisse zwischen den beiden Ansätzen?
Warum ist das wichtig? Nun ... was soll ich für eine neue mehrdimensionale Klasse in C ++ verwenden? size_t
oder int
?
struct Foo final // e.g., image, matrix, etc.
{
typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
typedef size_t size_type; // c.f., std::vector<>
dimension_type bar_; // maybe rows, or x
dimension_type baz_; // e.g., columns, or y
size_type size() const { ... } // STL-like interface
};
-1
wird an mehreren Stellen von Funktionen zurückgegeben, die einen Index zurückgeben, um "nicht gefunden" oder "außerhalb des gültigen Bereichs" anzuzeigen. Es wird auch vonCompare()
Funktionen (ImplementierungIComparable
) zurückgegeben. Ein 32-Bit-Int wird als Go-to-Type für eine allgemeine Zahl betrachtet. Ich hoffe, dass dies offensichtliche Gründe sind.Antworten:
Ja. Für bestimmte Arten von Anwendungen, z. B. Bildverarbeitung oder Array-Verarbeitung, ist es häufig erforderlich, auf Elemente zuzugreifen, die sich auf die aktuelle Position beziehen:
In diesen Arten von Anwendungen können Sie keine Bereichsprüfung mit Ganzzahlen ohne Vorzeichen durchführen, ohne sorgfältig zu überlegen:
Stattdessen müssen Sie Ihren Range Check-Ausdruck neu anordnen . Das ist der Hauptunterschied. Programmierer müssen sich auch an die ganzzahligen Konvertierungsregeln erinnern. Lesen Sie im Zweifelsfall erneut http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions
Viele Anwendungen müssen keine sehr großen Array-Indizes verwenden, aber sie müssen Bereichsprüfungen durchführen. Darüber hinaus sind viele Programmierer nicht dazu ausgebildet, diese Ausdrucksumordnungsturnen zu machen. Eine einzelne verpasste Gelegenheit öffnet die Tür zu einem Exploit.
C # wurde in der Tat für Anwendungen entwickelt, die nicht mehr als 2 ^ 31 Elemente pro Array benötigen. Beispielsweise muss eine Tabellenkalkulationsanwendung nicht mit so vielen Zeilen, Spalten oder Zellen umgehen. C # behandelt die Obergrenze, indem optional eine aktivierte Arithmetik für einen Codeblock mit einem Schlüsselwort aktiviert wird, ohne die Compileroptionen zu beeinträchtigen. Aus diesem Grund bevorzugt C # die Verwendung von Ganzzahlen mit Vorzeichen. Wenn diese Entscheidungen insgesamt betrachtet werden, ist dies sinnvoll.
C ++ ist einfach anders und es ist schwieriger, richtigen Code zu erhalten.
In Bezug auf die praktische Bedeutung, die vorzeichenbehaftete Arithmetik zuzulassen, um eine potenzielle Verletzung des "Grundsatzes des geringsten Erstaunens" zu beseitigen, ist OpenCV ein typisches Beispiel, das eine vorzeichenbehaftete 32-Bit-Ganzzahl für den Matrixelementindex, die Arraygröße, die Pixelkanalanzahl usw. verwendet Die Verarbeitung ist ein Beispiel für eine Programmierdomäne, die den relativen Array-Index stark verwendet. Ein vorzeichenloser ganzzahliger Unterlauf (negatives Ergebnis umbrochen) erschwert die Implementierung des Algorithmus erheblich.
quelle
Diese Antwort hängt wirklich davon ab, wer Ihren Code verwenden wird und welche Standards er sehen möchte.
size_t
ist eine Ganzzahl mit einem Zweck:Wenn Sie also mit der Größe von Objekten in Bytes arbeiten möchten, sollten Sie verwenden
size_t
. In vielen Fällen verwenden Sie diese Dimensionen / Indizes nicht zum Zählen von Bytes, aber die meisten Entwickler verwenden siesize_t
aus Konsistenzgründen.Beachten Sie, dass Sie immer verwenden sollten ,
size_t
wenn Ihre Klasse das Erscheinungsbild einer STL-Klasse haben soll. Alle STL-Klassen in der Spezifikation verwendensize_t
. Es ist gültig, dass der Compiler typedefsize_t
istunsigned int
, und es ist auch gültig, dass er typedef istunsigned long
. Wenn Sieint
oderlong
direkt verwenden, werden Sie irgendwann auf Compiler stoßen, bei denen eine Person, die glaubt, Ihre Klasse habe den STL-Stil befolgt, gefangen wird, weil Sie den Standard nicht befolgt haben.Für die Verwendung signierter Typen gibt es einige Vorteile:
int
, aber viel schwieriger, den Code zu überspielenunsigned int
.int32_t
unduint32_t
). Dies kann die API-Interoperabilität vereinfachenDer große Nachteil signierter Typen liegt auf der Hand: Sie verlieren die Hälfte Ihrer Domain. Eine vorzeichenbehaftete Nummer kann nicht so hoch sein wie eine vorzeichenlose Nummer. Als C / C ++ auf den Markt kam, war dies sehr wichtig. Man musste in der Lage sein, die volle Leistungsfähigkeit des Prozessors zu erreichen, und um dies zu erreichen, musste man vorzeichenlose Zahlen verwenden.
Für die Arten von Anwendungen, auf die .NET abzielt, bestand nicht so stark Bedarf an einem nicht signierten Volldomänenindex. Viele der Zwecke für solche Nummern sind in einer verwalteten Sprache einfach ungültig (man denke an Memory Pooling). Als .NET herauskam, waren 64-Bit-Computer eindeutig die Zukunft. Wir sind noch weit davon entfernt, den vollen Bereich einer 64-Bit-Ganzzahl zu benötigen. Daher ist es nicht mehr so schmerzhaft, ein Bit zu opfern wie zuvor. Wenn Sie wirklich 4 Milliarden Indizes benötigen, wechseln Sie einfach zur Verwendung von 64-Bit-Ganzzahlen. Im schlimmsten Fall läuft es auf einer 32-Bit-Maschine und ist etwas langsam.
Ich sehe den Handel als einen Zweck an. Wenn Sie über genügend Rechenleistung verfügen, um einen Teil Ihres Indextyps zu verschwenden, den Sie niemals verwenden werden, ist es praktisch, ihn nur zu tippen
int
oder zu verlassenlong
. Wenn Sie feststellen, dass Sie das letzte Stück wirklich wollten, sollten Sie wahrscheinlich auf die Signatur Ihrer Nummern achten.quelle
size()
warreturn bar_ * baz_;
; schafft das nicht jetzt ein potenzielles Problem mit Integer-Überlauf (Wrap-Around), das ich nicht hätte, wenn ich es nicht verwendet hättesize_t
?bar_ * baz_
Ganzzahl mit Vorzeichen überlaufen kann, aber keine Ganzzahl ohne Vorzeichen. Wenn wir uns auf C ++ beschränken, ist anzumerken, dass ein vorzeichenloser Überlauf in der Spezifikation definiert ist, ein vorzeichenbehafteter Überlauf jedoch ein undefiniertes Verhalten ist. Wenn also die Modulo-Arithmetik vorzeichenloser Ganzzahlen wünschenswert ist, verwenden Sie sie auf jeden Fall, da sie tatsächlich definiert sind!size()
übergelaufen ist, bist du in der Sprache UB land. (und im Modus, siehe weiter :) Wenn dann mit nur einem winzigen bisschen mehr die vorzeichenlose Multiplikation übergelaufen ist, befinden Sie sich im User-Code-Bug-Land - Sie würden eine falsche Größe zurückgeben. Ich glaube nicht, dass unsignierte Kunden hier viel kaufen.fwrapv
Ich denke, die Antwort von rwong oben hebt die Probleme bereits hervorragend hervor.
Ich werde meine 002 hinzufügen:
size_t
, das heißt, eine Größe, die ...... wird nur für Bereichsindizes benötigt
sizeof(type)==1
, wenn es sich um byte (char
) -Typen handelt. (Wir stellen jedoch fest, dass es kleiner sein kann als ein ptr-Typ :xxx::size_type
konnte in 99,9% der Fälle verwendet werden , auch wenn es ein signiertes Größe Typ waren. (vergleichenssize_t
)std::vector
und Freundesize_t
einen nicht signierten Typ für die Größe und Indexierung gewählt haben, wird von einigen als Designfehler angesehen. Ich stimme zu. (Im Ernst, nehmen Sie sich 5 Minuten Zeit und schauen Sie sich das Blitzgespräch CppCon 2016 an: Jon Kalb "unsigniert: Ein Leitfaden für besseren Code" .)size_t
diese Option , um mit der Standardbibliothek konsistent zu sein, oder verwenden Sie sie ( signiert )intptr_t
oderssize_t
für einfache und weniger fehleranfällige Indizierungsberechnungen.intptr_t
Sie, wenn Sie signiert werden möchten und die Größe von Maschinenwörtern möchten oder verwenden Siessize_t
.Um die Frage direkt zu beantworten, handelt es sich nicht nur um ein "historisches Artefakt", da die theoretische Frage , ob mehr als die Hälfte des ("Indizierungs-" oder) Adressraums adressiert werden muss , auf eine Art und Weise in einer Sprache auf niedriger Ebene adressiert werden muss C ++.
Im Nachhinein denke ich persönlich , dass es sich um einen Konstruktionsfehler handelt, den die Standardbibliothek
size_t
überall ohne Vorzeichen verwendet, auch wenn sie keine unformatierte Speichergröße darstellt, sondern eine Kapazität für eingegebene Daten, wie für die Sammlungen:Ich werde Jons Rat hier wiederholen :
(* 1) dh vorzeichenlose == Bitmaske, rechnen Sie niemals damit (hier trifft die erste Ausnahme zu - Sie benötigen möglicherweise einen Zähler, der umbrochen wird - dies muss ein vorzeichenloser Typ sein.)
(* 2) Mengen, die etwas bedeuten, worauf Sie zählen und / oder rechnen.
quelle
ssize_t
der signierte Pendant definiert wird, der einen beliebigen (Nicht-Mitglieder-) Zeiger speichern kann und daher möglicherweise größer ist?size_t
intptr_t
size_t
Definition ein wenig durcheinander gebracht. Siehe size_t vs. intptr und en.cppreference.com/w/cpp/types/size_t Heute etwas Neues gelernt. :-) Ich denke, der Rest der Argumente steht, ich werde sehen, ob ich die verwendeten Typen reparieren kann.Ich füge das nur aus Performancegründen hinzu, die ich normalerweise mit size_t benutze sicherzustellen, dass Fehlberechnungen einen Unterlauf verursachen, was bedeutet, dass beide Bereichsprüfungen (unter Null und über Größe ()) auf eins reduziert werden können:
mit signiertem int:
mit unsigned int:
quelle