size_t o int per dimensioni, indice, ecc

15

In C ++, size_t (o, più correttamente T::size_type che è "di solito" size_t ; cioè, un tipo unsigned ) viene utilizzato come valore di ritorno per size() , l'argomento su operator[] , ecc. (vedi std::vector , et. al.)

D'altra parte, i linguaggi .NET usano int (e, facoltativamente, long ) per lo stesso scopo; infatti, i linguaggi conformi a CLS sono non richiesti per supportare i tipi non firmati .

Dato che .NET è più recente di C ++, qualcosa mi dice che potrebbe esserci problemi utilizzando unsigned int anche per cose che "non possono" essere negative come un indice o una lunghezza di array. L'approccio C ++ "artefatto storico" è retrocompatibile? O ci sono veri e significativi compromessi di design tra i due approcci?

Perché questo è importante? Bene ... cosa dovrei usare per una nuova classe multidimensionale in C ++; size_t o 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
};
    
posta Ðаn 13.12.2016 - 21:58
fonte

4 risposte

9

Given that .NET is newer than C++, something tells me that there may be problems using unsigned int even for things that "can't possibly" be negative like an array index or length.

Sì. Per alcuni tipi di applicazioni come l'elaborazione di immagini o l'elaborazione di array, è spesso necessario accedere agli elementi relativi alla posizione corrente:

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

In questi tipi di applicazioni, non è possibile eseguire il controllo dell'intervallo con interi senza segno senza pensare attentamente:

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

Devi invece riorganizzare l'espressione di controllo dell'intervallo. Questa è la differenza principale. I programmatori devono anche ricordare le regole di conversione dei numeri interi. In caso di dubbio, rileggi link

Molte applicazioni non hanno bisogno di usare indici di array molto grandi, ma devono eseguire controlli di intervallo. Inoltre, molti programmatori non sono addestrati a fare questa ginnastica di riorganizzazione dell'espressione. Un'unica opportunità mancata apre la porta a un exploit.

C # è infatti progettato per quelle applicazioni che non avranno bisogno di più di 2 ^ 31 elementi per array. Ad esempio, un'applicazione per fogli di calcolo non deve occuparsi di molte righe, colonne o celle. C # si occupa del limite superiore disponendo di aritmetica facoltativa controllata che può essere abilitata per un blocco di codice con una parola chiave senza scherzare con le opzioni del compilatore. Per questo motivo, C # favorisce l'utilizzo del numero intero con segno. Quando queste decisioni vengono considerate del tutto, è logico.

C ++ è semplicemente diverso ed è più difficile ottenere il codice corretto.

Riguardo all'importanza pratica di consentire all'aritmetica firmata di rimuovere una potenziale violazione del "principio del minimo stupore", un caso in questione è OpenCV, che utilizza un intero con segno a 32 bit per l'indice di elemento matrice, la dimensione dell'array, il conteggio dei canali pixel, ecc. L'elaborazione delle immagini è un esempio di dominio di programmazione che usa pesantemente l'indice di matrice relativo. Underflow intero non firmato (risultato negativo avvolto attorno) complicherà seriamente l'implementazione dell'algoritmo.

    
risposta data 14.12.2016 - 05:57
fonte
14

Questa risposta dipende veramente da chi utilizzerà il tuo codice e da quali standard vogliono vedere.

size_t è una dimensione intera con uno scopo:

The type size_t is an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object. (C++11 specification 18.2.6)

Quindi, ogni volta che desideri lavorare con la dimensione degli oggetti in byte, dovresti usare size_t . Ora, in molti casi, non stai utilizzando queste dimensioni / indici per contare i byte, ma la maggior parte degli sviluppatori sceglie di utilizzare size_t per coerenza.

Si noti che si dovrebbe sempre utilizzare size_t se si desidera che la classe abbia l'aspetto di una classe STL. Tutte le classi STL nella specifica utilizzano size_t . È valido per il compilatore digitare typedef size_t per essere unsigned int , ed è anche valido per essere digitato in unsigned long . Se usi int o long direttamente, finirai per entrare in compilatori in cui una persona che pensa che la tua classe abbia seguito lo stile di STL viene intrappolata perché non hai seguito lo standard.

Per quanto riguarda l'utilizzo di tipi firmati, ci sono alcuni vantaggi:

  • Nomi più corti: è molto facile per le persone digitare int , ma è molto più difficile ingombrare il codice con unsigned int .
  • Un numero intero per ogni dimensione - Esiste solo un intero conforme a CLS di 32-bit, che è Int32. In C ++ ce ne sono due ( int32_t e uint32_t ). Questo può semplificare l'interoperabilità delle API

Il grosso svantaggio dei tipi firmati è ovvio: perdi metà del tuo dominio. Un numero firmato non può contare fino a un numero senza segno. Quando C / C ++ è venuto in giro, questo era molto importante. Uno doveva essere in grado di affrontare tutte le funzionalità del processore, e per farlo era necessario usare numeri non firmati.

Per i tipi di applicazioni .NET mirate, non c'era la necessità di un indice senza segno completo di dominio. Molti degli scopi di tali numeri sono semplicemente non validi in un linguaggio gestito (viene in mente il pooling della memoria). Inoltre, quando .NET è uscito, i computer a 64 bit erano chiaramente il futuro. Siamo molto lontani dall'avere bisogno dell'intera gamma di numeri interi a 64 bit, quindi sacrificare un po 'non è così doloroso come prima. Se hai davvero bisogno di 4 miliardi di indici, passa semplicemente a utilizzare numeri interi a 64 bit. Nel peggiore dei casi, lo esegui su una macchina a 32 bit ed è un po 'lento.

Vedo il commercio come uno di convenienza. Se ti capita di avere abbastanza potenza di calcolo che non ti dispiace sprecare un po 'del tuo tipo di indice che non userai mai e poi mai, allora è conveniente digitare int o long e allontanarti da esso. Se trovi che volevi davvero quell'ultimo bit, probabilmente dovresti aver prestato attenzione alla firma dei tuoi numeri.

    
risposta data 13.12.2016 - 22:45
fonte
4

Penso che la risposta di rwong sopra evidenzia in modo eccellente i problemi.

Aggiungerò il mio 002:

  • size_t , ovvero una dimensione che ...

    can store the maximum size of a theoretically possible object of any type (including array).

    ... è richiesto solo per gli indici di intervallo quando sizeof(type)==1 , cioè se si hanno a che fare con tipi di byte ( char ). (Ma notiamo che può essere più piccolo di un tipo ptr :

  • Pertanto, xxx::size_type potrebbe essere utilizzato nel 99,9% dei casi anche se si tratta di un tipo con dimensioni firmate. (confronta ssize_t )
  • Il fatto che std::vector e amici abbiano scelto size_t , un tipo non firmato , per la dimensione e l'indicizzazione è considerato da alcuni come difetto di progettazione. Concordo. (Seriamente, prendi 5 minuti e guarda la chiacchierata CppCon 2016: Jon Kalb "non firmata: una linea guida per un codice migliore" .)
  • Quando progetti un'API C ++ oggi, ti trovi in una posizione ristretta: usa size_t per essere coerente con la Libreria standard o usa (un firmato ) intptr_t o ssize_t per calcoli di indicizzazione inclini a bug facili e meno.
  • Non usare int32 o int64 - usa intptr_t se vuoi andare firmato, e vuoi dimensione parola macchina, o usare ssize_t .

Per rispondere direttamente alla domanda, non è interamente un "manufatto storico", in quanto il problema teorico di dover affrontare più della metà dello spazio di indirizzamento ("indicizzazione", o) deve essere, aehm, indirizzato in qualche modo in un linguaggio di basso livello come il C ++.

Con il senno di poi, io, personalmente , penso che sia un difetto di progettazione che la Libreria standard utilizza size_t senza segno in tutto il luogo anche dove non rappresenta una dimensione della memoria grezza, ma una capacità di dati digitati, come per le raccolte:

  • date le regole di promozione integer del C ++ - >
  • i tipi non firmati non fanno buoni candidati per i tipi "semantici" per qualcosa come una dimensione semanticamente non firmata.

Ripeterò il consiglio di Jon qui:

  • Seleziona i tipi per le operazioni che supportano (non l'intervallo di valori). (* 1)
  • Non utilizzare tipi non firmati nella tua API. Nasconde bug senza benefici al rialzo.
  • Non utilizzare "unsigned" per le quantità. (* 2)

(* 1) i.e. unsigned == bitmask, non eseguire mai calcoli matematici (qui viene visualizzata la prima eccezione - potrebbe essere necessario un contatore che esegue il wrapping - questo deve essere un tipo senza segno.)

(* 2) quantità che significa qualcosa su cui conti e / o fai matematica.

    
risposta data 14.12.2016 - 22:44
fonte
0

Aggiungerò solo che per motivi di prestazioni normalmente uso size_t, per assicurare che calcoli errati causano un underflow che significa che entrambi i controlli di intervallo (sotto zero e sopra size ()) possono essere ridotti a uno :

utilizzando l'int firmato:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

utilizzando unsigned int:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}
    
risposta data 20.12.2016 - 13:40
fonte

Leggi altre domande sui tag