Wann soll std :: size_t verwendet werden?


201

Ich frage mich nur, ob ich std::size_tstattdessen für Loops und andere Dinge verwenden soll int. Zum Beispiel:

#include <cstdint>

int main()
{
    for (std::size_t i = 0; i < 10; ++i) {
        // std::size_t OK here? Or should I use, say, unsigned int instead?
    }
}

Was ist im Allgemeinen die beste Vorgehensweise hinsichtlich des Verwendungszwecks std::size_t?

Antworten:


185

Eine gute Faustregel gilt für alles, was Sie in der Schleifenbedingung mit etwas vergleichen müssen, das natürlich ein std::size_tSelbst ist.

std::size_tist der Typ eines beliebigen sizeofAusdrucks und kann garantiert die maximale Größe eines Objekts (einschließlich eines Arrays) in C ++ ausdrücken. Durch die Erweiterung ist es auch garantiert groß genug für jeden Array-Index, so dass es ein natürlicher Typ für eine Schleife nach Index über ein Array ist.

Wenn Sie nur bis zu einer Zahl zählen, ist es möglicherweise natürlicher, entweder den Typ der Variablen zu verwenden, die diese Zahl enthält, oder ein intoder unsigned int(falls groß genug), da dies eine natürliche Größe für die Maschine sein sollte.


40
Es ist erwähnenswert , dass nicht verwenden , size_twenn Sie führen können , sollten Sicherheitslücken .
BlueRaja - Danny Pflughoeft

5
Int ist nicht nur "natürlich", sondern das Mischen von signiertem und nicht signiertem Typ kann ebenso zu Sicherheitslücken führen. Vorzeichenlose Indizes sind ein Problem und ein guter Grund, eine benutzerdefinierte Vektorklasse zu verwenden.
Jo So

2
@JoSo Es gibt auch ssize_tfür signierte Werte.
EntangledLoops

70

size_tist der Ergebnistyp des sizeofOperators.

Verwenden Sie diese Option size_tfür Variablen, die die Größe oder den Index in einem Array modellieren. size_tvermittelt Semantik: Sie wissen sofort, dass es sich um eine Größe in Bytes oder einen Index handelt und nicht nur um eine andere Ganzzahl.

Die Verwendung size_tzur Darstellung einer Größe in Bytes hilft außerdem dabei, den Code portabel zu machen.


32

Der size_tTyp soll die Größe von etwas angeben , so dass es natürlich ist, es zu verwenden, z. B. die Länge einer Zeichenfolge abzurufen und dann jedes Zeichen zu verarbeiten:

for (size_t i = 0, max = strlen (str); i < max; i++)
    doSomethingWith (str[i]);

Sie müssen natürlich auf Randbedingungen achten, da es sich um einen vorzeichenlosen Typ handelt. Die Grenze am oberen Ende ist in der Regel nicht so wichtig , da die maximalen in der Regel groß ist (obwohl es ist möglich , es zu bekommen). Die meisten Leute verwenden nur eine intfür solche Dinge, weil sie selten Strukturen oder Arrays haben, die groß genug werden, um die Kapazität davon zu überschreiten int.

Aber achten Sie auf Dinge wie:

for (size_t i = strlen (str) - 1; i >= 0; i--)

Dies führt aufgrund des Umbruchverhaltens vorzeichenloser Werte zu einer Endlosschleife (obwohl ich gesehen habe, dass Compiler davor warnen). Dies kann auch gelindert werden durch: (etwas schwerer zu verstehen, aber zumindest immun gegen Verpackungsprobleme):

for (size_t i = strlen (str); i-- > 0; )

Durch Verschieben des Dekrements in einen Nebeneffekt der Fortsetzungsbedingung nach der Überprüfung wird der Wert vor dem Dekrementieren auf Fortsetzung überprüft , es wird jedoch weiterhin der dekrementierte Wert innerhalb der Schleife verwendet (weshalb die Schleife len .. 1nicht ausgeführt wird len-1 .. 0).


14
Übrigens ist es eine schlechte Praxis, strlenjede Iteration einer Schleife aufzurufen . :) Sie können so etwas tun:for (size_t i = 0, len = strlen(str); i < len; i++) ...
Musiphil

1
Selbst wenn es sich um einen vorzeichenbehafteten Typ handelt, müssen Sie auf Randbedingungen achten, vielleicht sogar noch mehr, da ein vorzeichenbehafteter Ganzzahlüberlauf ein undefiniertes Verhalten ist.
Adrian McCarthy

2
Das korrekte Herunterzählen kann auf folgende (berüchtigte) Weise erfolgen:for (size_t i = strlen (str); i --> 0;)
Jo So

1
@JoSo, das ist eigentlich ein ziemlich ordentlicher Trick, obwohl ich nicht sicher bin, ob mir die Einführung des -->Operators "geht an" gefällt (siehe stackoverflow.com/questions/1642028/… ). Haben Sie Ihren Vorschlag in die Antwort aufgenommen.
Paxdiablo

Können Sie eine einfache if (i == 0) break;am Ende der for-Schleife machen (z for (size_t i = strlen(str) - 1; ; --i). B. (Ich mag Ihre zwar besser, frage mich aber nur, ob dies genauso gut funktionieren würde).
RastaJedi

13

Per Definition size_tist das Ergebnis des sizeofOperators. size_twurde erstellt, um sich auf Größen zu beziehen.

Bei der Häufigkeit, mit der Sie etwas tun (in Ihrem Beispiel 10), geht es nicht um Größen. Warum also verwenden size_t? int, oder unsigned intsollte in Ordnung sein.

Natürlich ist es auch relevant, was Sie iinnerhalb der Schleife tun . Wenn Sie es an eine Funktion übergeben, die unsigned intbeispielsweise eine Auswahl übernimmt unsigned int.

In jedem Fall empfehle ich, implizite Typkonvertierungen zu vermeiden. Machen Sie alle Typkonvertierungen explizit.


10

size_tist eine sehr lesenswerte Methode, um die Größe eines Elements anzugeben - Länge einer Zeichenfolge, Anzahl der Bytes, die ein Zeiger benötigt usw. Es ist auch plattformübergreifend portierbar - Sie werden feststellen, dass sich 64-Bit und 32-Bit gut mit Systemfunktionen verhalten und size_t- etwas, das unsigned intmöglicherweise nicht funktioniert (z. B. wann sollten Sie verwendenunsigned long


9

kurze Antwort:

fast nie

lange Antwort:

Wann immer Sie einen Zeichenvektor benötigen, der größer als 2 GB auf einem 32-Bit-System ist. In jedem anderen Anwendungsfall ist die Verwendung eines signierten Typs viel sicherer als die Verwendung eines nicht signierten Typs.

Beispiel:

std::vector<A> data;
[...]
// calculate the index that should be used;
size_t i = calc_index(param1, param2);
// doing calculations close to the underflow of an integer is already dangerous

// do some bounds checking
if( i - 1 < 0 ) {
    // always false, because 0-1 on unsigned creates an underflow
    return LEFT_BORDER;
} else if( i >= data.size() - 1 ) {
    // if i already had an underflow, this becomes true
    return RIGHT_BORDER;
}

// now you have a bug that is very hard to track, because you never 
// get an exception or anything anymore, to detect that you actually 
// return the false border case.

return calc_something(data[i-1], data[i], data[i+1]);

Das signierte Äquivalent von size_tist ptrdiff_tnicht int. intIn den meisten Fällen ist die Verwendung jedoch immer noch viel besser als size_t. ptrdiff_tist longauf 32- und 64-Bit-Systemen.

Dies bedeutet, dass Sie immer zu und von size_t konvertieren müssen, wenn Sie mit einem std :: -Container interagieren, der nicht sehr schön ist. Auf einer laufenden nativen Konferenz erwähnten die Autoren von c ++ jedoch, dass das Entwerfen von std :: vector mit einer vorzeichenlosen Größe_t ein Fehler war.

Wenn Ihr Compiler Sie bei impliziten Konvertierungen von ptrdiff_t nach size_t warnt, können Sie dies mit der Konstruktorsyntax explizit machen:

calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);

Wenn Sie nur eine Sammlung iterieren möchten, ohne Grenzen zu überschreiten, verwenden Sie den Bereich basierend auf:

for(const auto& d : data) {
    [...]
}

Hier einige Worte von Bjarne Stroustrup (C ++ - Autor), wie man einheimisch wird

Für einige Leute ist dieser signierte / nicht signierte Designfehler in der STL Grund genug, nicht den std :: vector zu verwenden, sondern eine eigene Implementierung.


1
Ich verstehe, woher sie kommen, aber ich finde es immer noch komisch zu schreiben for(int i = 0; i < get_size_of_stuff(); i++). Nun, sicher, Sie möchten vielleicht nicht viele Raw-Loops machen, aber - komm schon, Sie verwenden sie auch.
Einpoklum

Der einzige Grund, warum ich Raw-Loops verwende, ist, dass die C ++ - Algorithmusbibliothek ziemlich schlecht gestaltet ist. Es gibt Sprachen wie Scala, die eine viel bessere und weiterentwickelte Bibliothek haben, um Sammlungen zu bearbeiten. Dann ist der Anwendungsfall von Rohschleifen so gut wie beseitigt. Es gibt auch Ansätze, um c ++ mit einer neuen und besseren STL zu verbessern, aber ich bezweifle, dass dies innerhalb des nächsten Jahrzehnts geschehen wird.
Arne

1
Ich bekomme das vorzeichenlose i = 0; assert (i-1, MAX_INT); aber ich verstehe nicht, warum Sie sagen "Wenn ich bereits einen Unterlauf hatte, wird dies wahr", weil das Verhalten der Arithmetik auf vorzeichenlosen Ints immer definiert ist, dh. Das Ergebnis ist das Ergebnis Modulo der Größe der größten darstellbaren Ganzzahl. Wenn also i == 0 ist, wird i-- zu MAX_INT und dann wird i ++ wieder zu 0.
Mabraham

@mabraham Ich habe genau hingeschaut, und Sie haben Recht, mein Code ist nicht der beste, um das Problem zu zeigen. Normalerweise ist dies x + 1 < yäquivalent zu x < y - 1, aber sie sind nicht mit nicht berichtigenden ganzen Zahlen. Dies kann leicht zu Fehlern führen, wenn Dinge transformiert werden, von denen angenommen wird, dass sie gleichwertig sind.
Arne

8

Verwenden Sie std :: size_t zum Indizieren / Zählen von Arrays im C-Stil.

Für STL-Container haben Sie (zum Beispiel) vector<int>::size_type, die zum Indizieren und Zählen von Vektorelementen verwendet werden sollten.

In der Praxis handelt es sich normalerweise um nicht signierte Ints, dies kann jedoch nicht garantiert werden, insbesondere wenn benutzerdefinierte Allokatoren verwendet werden.


2
Bei gcc unter Linux std::size_tist dies normalerweise unsigned long(8 Bytes auf 64-Bit-Systemen) und nicht unisgned int(4 Bytes).
Rafak

5
Arrays im C-Stil werden jedoch nicht von indiziert size_t, da die Indizes negativ sein können. Man könnte es size_tfür die eigene Instanz eines solchen Arrays verwenden, wenn man jedoch nicht negativ werden möchte.
Johannes Schaub - litb

Sind Vergleiche mit U64 so schnell wie Vergleiche mit U32? Ich habe schwere Leistungseinbußen für die Verwendung von U8s und U16s als Loop-Sentinels eingeplant, aber ich weiß nicht, ob Intel sich auf 64s zusammengetan hat.
Crashworks

2
Da die Indexierung von Arrays im C-Stil der Verwendung eines Operators +für Zeiger entspricht, scheint dies diejenige ptrdiff_tzu sein, die für Indizes verwendet werden soll.
Pavel Minaev

8
Was vector<T>::size_type(und ebenso für alle anderen Container) size_tbetrifft , so ist es eigentlich ziemlich nutzlos, da es effektiv garantiert ist - es ist typisiert Allocator::size_typeund für Einschränkungen in Bezug auf Container siehe 20.1.5 / 4 - insbesondere size_typemuss sein size_tund difference_typemüssen sein ptrdiff_t. Natürlich std::allocator<T>erfüllt der Standard diese Anforderungen. Also benutze einfach die kürzere size_tund kümmere dich nicht um den Rest des Loses :)
Pavel Minaev

7

Bald werden die meisten Computer 64-Bit-Architekturen mit 64-Bit-Betriebssystemen sein: Es werden Programme ausgeführt, die auf Containern mit Milliarden von Elementen ausgeführt werden. Dann Sie müssen verwenden , size_tanstatt intals Schleifenindex, sonst wird Ihr Index wird umschlingen am 2 ^ 32: te Element auf beiden 32- und 64-Bit - Systeme.

Bereite dich auf die Zukunft vor!


Ihr Argument geht nur so weit, dass man long inteher ein als ein braucht int. Wenn dies size_tauf einem 64-Bit-Betriebssystem relevant ist, war es auf einem 32-Bit-Betriebssystem genauso relevant.
Einpoklum

4

Seien Sie bei der Verwendung von size_t vorsichtig mit dem folgenden Ausdruck

size_t i = containner.find("mytoken");
size_t x = 99;
if (i-x>-1 && i+x < containner.size()) {
    cout << containner[i-x] << " " << containner[i+x] << endl;
}

Sie werden im if-Ausdruck falsch, unabhängig davon, welchen Wert Sie für x haben. Ich habe mehrere Tage gebraucht, um dies zu realisieren (der Code ist so einfach, dass ich keinen Komponententest durchgeführt habe), obwohl es nur wenige Minuten dauerte, um die Ursache des Problems herauszufinden. Ich bin mir nicht sicher, ob es besser ist, einen Cast zu machen oder Null zu verwenden.

if ((int)(i-x) > -1 or (i-x) >= 0)

Beide Wege sollten funktionieren. Hier ist mein Testlauf

size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;

Die Ausgabe: i-7 = 18446744073709551614 (int) (i-7) = - 2

Ich hätte gerne die Kommentare anderer.


2
Bitte beachten Sie, dass (int)(i - 7)es sich um einen Unterlauf handelt, der intanschließend umgewandelt wird, während int(i) - 7es sich nicht um einen Unterlauf handelt, da Sie zuerst iin einen konvertieren intund dann subtrahieren 7. Außerdem fand ich Ihr Beispiel verwirrend.
hochl

Mein Punkt ist, dass int normalerweise sicherer ist, wenn Sie Subtraktionen durchführen.
Kemin Zhou

4

size_t wird von verschiedenen Bibliotheken zurückgegeben, um anzuzeigen, dass die Größe dieses Containers ungleich Null ist. Sie verwenden es, wenn Sie einmal zurück sind: 0

In Ihrem obigen Beispiel ist das Schleifen eines size_t jedoch ein potenzieller Fehler. Folgendes berücksichtigen:

for (size_t i = thing.size(); i >= 0; --i) {
  // this will never terminate because size_t is a typedef for
  // unsigned int which can not be negative by definition
  // therefore i will always be >= 0
  printf("the never ending story. la la la la");
}

Die Verwendung von Ganzzahlen ohne Vorzeichen kann zu solchen subtilen Problemen führen. Daher bevorzuge ich es, size_t nur zu verwenden, wenn ich mit Containern / Typen interagiere, die dies erfordern.


Jeder scheint size_t in einer Schleife zu verwenden, ohne sich um diesen Fehler zu
kümmern

-2

size_tist ein vorzeichenloser Typ, der den maximalen ganzzahligen Wert für Ihre Architektur enthalten kann. Er ist daher vor ganzzahligen Überläufen aufgrund von Vorzeichen (vorzeichenbehaftetes int, 0x7FFFFFFFerhöht um 1 ergibt -1) oder kurzer Größe (vorzeichenloses kurzes int 0xFFFF, erhöht um 1) geschützt 0).

Es wird hauptsächlich in der Array-Indizierung / Schleifen / Adress-Arithmetik usw. verwendet. Funktionen wie memset()und werden size_tnur akzeptiert , da Sie theoretisch möglicherweise einen Speicherblock mit einer Größe haben 2^32-1(auf einer 32-Bit-Plattform).

Für solche einfachen Schleifen stören Sie nicht und verwenden Sie nur int.


-3

size_t ist ein vorzeichenloser Integraltyp, der die größte Ganzzahl auf Ihrem System darstellen kann. Verwenden Sie es nur, wenn Sie sehr große Arrays, Matrizen usw. benötigen.

Einige Funktionen geben ein size_t zurück und Ihr Compiler warnt Sie, wenn Sie versuchen, Vergleiche durchzuführen.

Vermeiden Sie dies, indem Sie einen geeigneten signierten / nicht signierten Datentyp verwenden oder einfach für einen schnellen Hack typisieren.


4
Verwenden Sie es nur, wenn Sie Fehler und Sicherheitslücken vermeiden möchten.
Craig McQueen

2
Es ist möglicherweise nicht in der Lage, die größte Ganzzahl auf Ihrem System darzustellen.
Adrian McCarthy

-4

size_t ist int ohne Vorzeichen int. Wann immer Sie unsigned int möchten, können Sie es verwenden.

Ich benutze es, wenn ich die Größe des Arrays angeben möchte, Zähler ect ...

void * operator new (size_t size); is a good use of it.

10
Eigentlich ist es nicht unbedingt dasselbe wie int ohne Vorzeichen. Es ist nicht signiert, aber es ist möglicherweise größer (oder kleiner, obwohl ich keine Plattformen kenne, auf denen dies zutrifft) als ein int.
Todd Gamblin

Beispielsweise kann es sich bei einem 64-Bit-Computer size_tum eine vorzeichenlose 64-Bit-Ganzzahl handeln, während es sich bei einem 32-Bit-Computer nur um eine vorzeichenlose 32-Bit-Ganzzahl handelt.
HerpDerpington
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.