Quando usare std :: size_t?


201

Mi chiedo solo che dovrei usare std::size_tper loop e cose invece di int? Per esempio:

#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?
    }
}

In generale, qual è la migliore pratica riguardo a quando usare std::size_t?

Risposte:


186

Una buona regola empirica è per tutto ciò che è necessario confrontare nella condizione del ciclo con qualcosa che è naturalmente un std::size_tsé.

std::size_tè il tipo di qualsiasi sizeofespressione e, come è garantito, è in grado di esprimere la dimensione massima di qualsiasi oggetto (incluso qualsiasi array) in C ++. Per estensione, è anche garantito che sia abbastanza grande per qualsiasi indice di array, quindi è un tipo naturale per un ciclo per indice su un array.

Se stai solo contando fino a un numero, potrebbe essere più naturale utilizzare il tipo di variabile che contiene quel numero o un into unsigned int(se abbastanza grande) in quanto questi dovrebbero avere una dimensione naturale per la macchina.


41
Vale la pena ricordare che non utilizzare size_tquando si dovrebbe può portare a bug di sicurezza .
BlueRaja - Danny Pflughoeft,

5
Int non è solo "naturale", ma mescolare tipi firmati e non firmati può portare anche a bachi di sicurezza. Gli indici senza segno sono una seccatura da gestire e una buona ragione per usare una classe vettoriale personalizzata.
Jo So,

2
@JoSo C'è anche ssize_tper i valori firmati.
EntangledLoops

70

size_tè il tipo di risultato sizeofdell'operatore.

Utilizzare size_tper variabili che modellano le dimensioni o l'indice in un array. size_ttrasmette semantica: sai immediatamente che rappresenta una dimensione in byte o un indice, piuttosto che solo un altro numero intero.

Inoltre, l'utilizzo size_tdi rappresentare una dimensione in byte aiuta a rendere il codice portatile.


32

Il size_ttipo ha lo scopo di specificare la dimensione di qualcosa, quindi è naturale usarlo, ad esempio, ottenere la lunghezza di una stringa e quindi elaborare ogni carattere:

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

Ovviamente devi fare attenzione alle condizioni al contorno, dato che è un tipo senza segno. Il limite all'estremità superiore non è in genere così importante poiché il massimo è generalmente ampio (sebbene sia possibile arrivarci). La maggior parte delle persone usa semplicemente una cosa intper quel genere di cose perché raramente ha strutture o array che diventano abbastanza grandi da superare la capacità di ciò int.

Ma fai attenzione a cose come:

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

che causerà un ciclo infinito a causa del comportamento di wrapping di valori non firmati (anche se ho visto i compilatori mettere in guardia contro questo). Questo può anche essere alleviato da (leggermente più difficile da capire ma almeno immune da problemi di avvolgimento):

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

Spostando il decremento in un effetto collaterale post-check della condizione di continuazione, questo verifica la continuazione del valore prima del decremento, ma utilizza comunque il valore decrementato all'interno del loop (motivo per cui il loop viene eseguito len .. 1anziché anziché len-1 .. 0).


14
A proposito, è una cattiva pratica invocare strlenogni iterazione di un ciclo. :) Puoi fare qualcosa del genere:for (size_t i = 0, len = strlen(str); i < len; i++) ...
musiphil

1
Anche se fosse un tipo con segno, devi fare attenzione alle condizioni al contorno, forse ancora di più poiché l'overflow di numeri interi con segno è un comportamento indefinito.
Adrian McCarthy,

2
Il conto alla rovescia può essere eseguito correttamente nel modo (famigerato) seguente:for (size_t i = strlen (str); i --> 0;)
Jo So,

1
@JoSo, in realtà è un bel trucco, anche se non sono sicuro che mi piace l'introduzione dell'operatore -->"va a" (vedi stackoverflow.com/questions/1642028/… ). Ho incorporato il tuo suggerimento nella risposta.
paxdiablo,

Puoi fare un semplice if (i == 0) break;alla fine del ciclo for (ad es for (size_t i = strlen(str) - 1; ; --i). (Mi piace meglio il tuo però, ma mi chiedo solo se funzionerebbe altrettanto bene).
RastaJedi,

13

Per definizione, size_tè il risultato sizeofdell'operatore. size_tè stato creato per fare riferimento alle dimensioni.

Il numero di volte in cui fai qualcosa (10, nel tuo esempio) non riguarda le dimensioni, quindi perché usarle size_t? int, o unsigned int, dovrebbe essere ok.

Naturalmente è anche rilevante ciò che fai iall'interno del loop. Se lo si passa a una funzione che richiede unsigned int, ad esempio, scegliere unsigned int.

In ogni caso, consiglio di evitare conversioni di tipo implicite. Rendi esplicite tutte le conversioni di tipo.


10

size_tè un modo molto leggibile per specificare la dimensione di un elemento - lunghezza di una stringa, quantità di byte che un puntatore accetta, ecc. È anche portatile su più piattaforme - scoprirai che 64 bit e 32 bit si comportano bene con le funzioni di sistema e size_t- qualcosa che unsigned intpotrebbe non fare (ad esempio quando si dovrebbe usareunsigned long


9

risposta breve:

quasi mai

risposta lunga:

Ogni volta che devi avere un vettore di carattere maggiore di 2 GB su un sistema a 32 bit. In ogni altro caso d'uso, l'utilizzo di un tipo con segno è molto più sicuro rispetto all'utilizzo di un tipo senza segno.

esempio:

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]);

L'equivalente firmato di size_tè ptrdiff_t, no int. Ma l'utilizzo intè ancora molto meglio nella maggior parte dei casi di size_t. ptrdiff_tè longsu sistemi a 32 e 64 bit.

Questo significa che devi sempre convertire da e verso size_t ogni volta che interagisci con uno std :: containers, che non è molto bello. Ma in una conferenza nativa in corso gli autori di c ++ hanno menzionato che progettare std :: vector con un size_t non firmato è stato un errore.

Se il tuo compilatore ti dà avvisi su conversioni implicite da ptrdiff_t a size_t, puoi renderlo esplicito con la sintassi del costruttore:

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

se vuoi solo iterare una raccolta, senza limiti, usa l'intervallo basato su:

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

ecco alcune parole di Bjarne Stroustrup (autore C ++) per diventare nativi

Per alcune persone questo errore di progettazione firmato / non firmato nell'STL è una ragione sufficiente per non utilizzare lo std :: vector, ma una propria implementazione.


1
Capisco da dove vengono, ma penso ancora che sia strano scrivere for(int i = 0; i < get_size_of_stuff(); i++). Ora, certo, potresti non voler fare molti loop grezzi, ma - dai, li usi anche tu.
einpoklum,

L'unico motivo per cui utilizzo loop non elaborati è perché la libreria di algoritmi c ++ è progettata in modo piuttosto errato. Ci sono lingue, come Scala, che hanno una biblioteca molto migliore e più evoluta per operare sulle collezioni. Quindi il caso d'uso dei loop grezzi viene praticamente eliminato. Esistono anche approcci per migliorare il c ++ con un nuovo e migliore STL, ma dubito che ciò accadrà entro il prossimo decennio.
Arne,

1
Ottengo quel unsigned i = 0; assert (i-1, MAX_INT); ma non capisco perché dici "se ho già avuto un underflow, questo diventa vero" perché il comportamento dell'aritmetica sugli ints non firmati è sempre definito, cioè. il risultato è il risultato modulo della dimensione del numero intero più grande rappresentabile. Quindi se i == 0, allora i-- diventa MAX_INT e poi i ++ diventa di nuovo 0.
mabraham,

@mabraham Ho guardato bene e hai ragione, il mio codice non è il migliore per mostrare il problema. Normalmente questo x + 1 < yequivale a x < y - 1, ma non sono con numeri interi non desiderati. Ciò può facilmente introdurre bug quando si trasformano cose che si presume equivalenti.
Arne,

8

Utilizzare std :: size_t per indicizzare / contare le matrici in stile C.

Per i contenitori STL, avrai (per esempio) vector<int>::size_type, che dovrebbe essere usato per indicizzare e contare gli elementi vettoriali.

In pratica, di solito sono entrambi ints senza segno, ma non è garantito, soprattutto quando si utilizzano allocatori personalizzati.


2
Con gcc su linux, di std::size_tsolito è unsigned long(8 byte su sistemi a 64 bit) anziché unisgned int(4 byte).
Rafak,

5
Le matrici di tipo C non sono indicizzate da size_t, poiché gli indici possono essere negativi. size_tTuttavia, si potrebbe usare per la propria istanza di tale array se non si vuole diventare negativi.
Johannes Schaub - litb

I confronti su U64 sono veloci quanto i confronti su U32? Ho cronometrato severamente le prestazioni per l'utilizzo di u8s e u16s come sentinel di loop, ma non so se Intel abbia messo insieme i loro comportamenti su 64s.
Crashworks,

2
Poiché l'indicizzazione di array in stile C equivale all'uso dell'operatore +sui puntatori, sembrerebbe quello ptrdiff_tda utilizzare per gli indici.
Pavel Minaev,

8
Per quanto riguarda vector<T>::size_type(e idem per tutti gli altri contenitori), in realtà è piuttosto inutile, perché è effettivamente garantito che sia size_t- è tipicamente redatto Allocator::size_type, e per le restrizioni su questo rispetto ai contenitori vedi 20.1.5 / 4 - in particolare, size_typedeve essere size_te difference_typedeve essere ptrdiff_t. Naturalmente, il default std::allocator<T>soddisfa tali requisiti. Quindi usa solo il più corto size_te non preoccuparti del resto del lotto :)
Pavel Minaev,

7

Presto la maggior parte dei computer saranno architetture a 64 bit con sistema operativo a 64 bit: es eseguiranno programmi che operano su contenitori di miliardi di elementi. Quindi è necessario utilizzare size_tinvece intcome indice di loop, altrimenti l'indice verrà spostato sull'elemento 2 ^ 32: th, su entrambi i sistemi a 32 e 64 bit.

Preparati per il futuro!


Il tuo argomento arriva solo al significato che uno ha bisogno di un long intpiuttosto che un int. Se size_tè rilevante su un sistema operativo a 64 bit era altrettanto rilevante su un sistema operativo a 32 bit.
einpoklum,

4

Quando si utilizza size_t, prestare attenzione alla seguente espressione

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;
}

Diventerai falso nell'espressione if indipendentemente dal valore che hai per x. Mi ci sono voluti diversi giorni per rendermene conto (il codice è così semplice che non ho fatto unit test), anche se ci sono voluti solo pochi minuti per capire l'origine del problema. Non sono sicuro che sia meglio fare un cast o usare zero.

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

Entrambi i modi dovrebbero funzionare. Ecco la mia corsa di prova

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

L'output: i-7 = 18446744073709551614 (int) (i-7) = - 2

Vorrei i commenti degli altri.


2
si prega di notare che (int)(i - 7)è un underflow che viene lanciato in intseguito, mentre int(i) - 7non è un underflow poiché si converte prima iin un int, quindi si sottrae 7. Inoltre ho trovato confuso il tuo esempio.
Hochl,

Il punto è che int è generalmente più sicuro quando si eseguono sottrazioni.
Kemin Zhou,

4

size_t viene restituito da varie librerie per indicare che la dimensione di quel contenitore è diversa da zero. Lo usi quando torni una volta: 0

Tuttavia, nell'esempio sopra riportato, il looping su size_t è un potenziale bug. Considera quanto segue:

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");
}

l'uso di numeri interi senza segno ha il potenziale per creare questo tipo di problemi sottili. Pertanto, preferisco usare size_t solo quando interagisco con contenitori / tipi che lo richiedono.


Everone sembra usare size_t in loop senza preoccuparsi di questo bug, e l'ho imparato a mie
spese

-2

size_tè un tipo senza segno che può contenere il valore intero massimo per la tua architettura, quindi è protetto da overflow di numeri interi a causa del segno (int con segno 0x7FFFFFFFincrementato di 1 ti darà -1) o dimensioni ridotte (senza segno int int 0xFFFF incrementato di 1 ti darà 0).

Viene utilizzato principalmente nell'array di indicizzazione / loop / indirizzi dell'array e così via. Funzionalità simili memset()e simili accettano size_tsolo, perché in teoria potresti avere un blocco di memoria di dimensioni 2^32-1(su piattaforma a 32 bit).

Per loop così semplici non preoccuparti e usa solo int.


-3

size_t è un tipo integrale senza segno, che può rappresentare il numero intero più grande sul tuo sistema. Usalo solo se hai bisogno di matrici, matrici ecc. Molto grandi

Alcune funzioni restituiscono size_t e il compilatore ti avviserà se provi a fare confronti.

Evitatelo utilizzando un tipo di dati firmato / non firmato appropriato o semplicemente digitando per un hack rapido.


4
Usalo solo se vuoi evitare bug e falle di sicurezza.
Craig McQueen,

2
Potrebbe non essere in grado di rappresentare il numero intero più grande sul tuo sistema.
Adrian McCarthy,

-4

size_t non è firmato int. così ogni volta che vuoi un unsigned int puoi usarlo.

Lo uso quando desidero specificare la dimensione dell'array, la controprova ...

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

10
In realtà non è necessariamente lo stesso di unsigned int. Si è firmato, ma potrebbe essere più grande (o credo più piccolo anche se non so di eventuali piattaforme in cui questo è vero) che un int.
Todd Gamblin,

Ad esempio, su una macchina a 64 bit size_tpotrebbe essere un numero intero a 64 bit senza segno, mentre su una macchina a 32 bit è solo un numero intero senza segno a 32 bit.
HerpDerpington,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.