2016-01-06 10 views
14

Sto studiando uno standard per il mio team utilizzando size_t rispetto a int (o long, ecc.). Il più grande svantaggio che ho notato è che prendere la differenza tra due oggetti size_t può causare problemi (non sono sicuro di problemi specifici - forse qualcosa non è stato completato da 2 e il compilatore ha firmato/non firmato). Ho scritto un programma rapido in C++ usando il compilatore V120 VS2013 che mi ha permesso di fare quanto segue:È sicuro prendere la differenza di due oggetti size_t?

#include <iostream> 

main() 
{ 
    size_t a = 10; 
    size_t b = 100; 
    int result = a - b; 
} 

Il programma ha provocato -90, che anche se corretta, mi rende nervoso per tipo discordanze, firmato/problemi non firmati, o semplicemente un comportamento indefinito se la dimensione_t accade per essere utilizzata in matematica complessa.

La mia domanda è se è sicuro fare matematica con gli oggetti size_t, in particolare, facendo la differenza? Sto considerando l'utilizzo di size_t come standard per cose come gli indici. Ho visto alcuni post interessanti sull'argomento qui, ma non affrontano il problema matematico (o l'ho perso).

What type for subtracting 2 size_t's?

typedef for a signed type that can contain a size_t?

+2

Se la sottrazione risulta in un numero negativo, [si avvolge] (https://stackoverflow.com/questions/1269019/what-should-happen-to-the-negation-of-a-size-tie -sizeofstruct-foo) secondo [complimento di due] (https://en.wikipedia.org/wiki/Two%27s_complement) – CoryKramer

+4

Il modo in cui lo hai, no non è sicuro. Ma non a causa della matematica stessa. Stai lanciando un risultato 'size_t' (che probabilmente sarà' unsigned long') a un 'int' (signed), che probabilmente è un tipo di dati più piccolo. –

+5

Il tipo di differenza corrispondente di size_t è ptrdiff_t, non int –

risposta

14

Questo non è garantito il funzionamento portabile, ma non è neanche UB. Il codice deve essere eseguito senza errori, ma il valore risultante int è definito dall'implementazione. Quindi, se lavori su piattaforme che garantiscono il comportamento desiderato, questo va bene (purché la differenza possa essere rappresentata da un int ovviamente), altrimenti, usa solo i tipi firmati ovunque (vedi l'ultimo paragrafo).

Sottraendo due std::size_t s produrrà un nuovo std::size_t e il suo valore sarà determinato avvolgendo. Nel tuo esempio, supponendo 64 bit size_t, a - b sarà uguale a 18446744073709551526. Questo non si adatta a un (comunemente usato 32 bit) int, quindi un valore definito di implementazione viene assegnato a result.

Per essere onesti, consiglierei di non usare interi senza segno per altro che la magia dei bit. Diversi membri del comitato di serie d'accordo con me: https://channel9.msdn.com/Events/GoingNative/2013/Interactive-Panel-Ask-Us-Anything 09:50, 42:40, 1:02:50

Regola generale (parafrasando Chandler Carruth dal video qui sopra): Se potessi contare da soli, utilizzare int , altrimenti utilizzare std::int64_t.


A meno che il suo rango di conversione è inferiore a int, per esempio se std::size_t è unsigned short. In tal caso, il risultato è uno int e tutto funzionerà correttamente (a meno che lo int non sia più largo di short). Tuttavia

  1. Non conosco nessuna piattaforma che lo faccia.
  2. Questo sarebbe ancora specifico per la piattaforma, vedere il primo paragrafo.
+0

Ci sono molte piattaforme in cui 'sizeof (int) == sizeof (short)' e 'sizeof (int) == sizeof (size_t)' (tutti i 16 bit). Che è quasi su ogni microcontrollore a 8 e 16 bit il caso (AVR, PIC10-18, MSP430, ...) – 12431234123412341234123

5

Se non si utilizza size_t, si sono avvitati: size_t è il solo tipo che esiste da utilizzare per dimensioni di memoria, e che deve pertanto garanzia di essere sempre abbastanza grande per quello scopo. (uintptr_t è abbastanza simile, ma non è né il primo di questo tipo, né è utilizzato dalle librerie standard, né è disponibile senza includere stdint.h.) Se si utilizza uno int, è possibile ottenere un comportamento non definito quando le allocazioni superano 2GiB di indirizzo spazio (o 32kiB se ci si trova su una piattaforma in cui int ha solo 16 bit!), anche se la macchina ha più memoria e si sta eseguendo in modalità 64 bit.

Se è necessaria una differenza di size_t che potrebbe diventare negativa, utilizzare la variante firmata ssize_t.

+1

"usa la variante firmata' ssize_t' "... come? Vuoi eseguire il cast prima o dopo la sottrazione? Che dire della gamma persa? –

+0

@BenVoigt L'intervallo perso con 'ssize_t' è un bit, l'intervallo perso di' int' è spesso di 32 bit, potrebbe anche essere di 48 bit (se 'int' è 16 bit e' size_t' è 64 bit). Ergo: Usare 'ssize_t' è decisamente molto meglio dell'uso di' int'. – cmaster

+1

@BenVoigt Non importa quando si esegue il cast: se si verifica un overflow, si viene avvitati comunque, con 'int' e' ssize_t'. Tutti i controlli di integrità devono essere eseguiti prima che venga calcolata la differenza. – cmaster

4

Il tipo size_t non è firmato. La sottrazione di qualsiasi due valori size_t è definita-comportamento

Tuttavia, in primo luogo, il risultato è definito dall'implementazione se un valore più grande viene sottratto da uno più piccolo. Il risultato è il valore matematico, ridotto al più piccolo residuo positivo modulo SIZE_T_MAX + 1. Ad esempio, se il valore più grande di size_t è 65535 e il risultato della sottrazione di due valori size_t è -3, il risultato sarà 65536 - 3 = 65533. Su un compilatore o macchina diverso con size_t diverso, il valore numerico sarà diverso.

In secondo luogo, un valore size_t potrebbe non rientrare nell'intervallo del tipo int. In questo caso, otteniamo un secondo risultato definito dall'implementazione derivante dalla conversione forzata. In questa situazione, qualsiasi comportamento può essere applicato; deve solo essere documentato dall'implementazione e la conversione non deve fallire. Ad esempio, il risultato potrebbe essere bloccato nell'intervallo int, producendo INT_MAX. Un comportamento comune visto su due complementi di macchine (praticamente tutti) nella conversione di tipi di unsigned più larghi (o uguali a larghezza) in stringhe più strette è il troncamento bit semplice: vengono presi abbastanza bit dal valore unsigned per riempire il valore firmato, incluso il suo segno po.

A causa del modo in cui funziona il complemento a due, se il risultato astratto originale aritmeticamente corretto si adatta a int, la conversione produrrà quel risultato.

Ad esempio, supponiamo che la sottrazione di una coppia di valori a 64 bit size_t su una macchina a complemento a due produce il valore aritmetico astratto -3, che diventa il valore positivo 0xFFFFFFFFFFFFFFFD. Quando questo è forzato in un 32 bit int, il comportamento comune visto in molti compilatori per le macchine a complemento di due è che i 32 bit inferiori vengono presi come l'immagine del risultante : 0xFFFFFFFD. E, ovviamente, questo è solo il valore -3 in 32 bit.

Quindi il risultato è che il codice è de facto abbastanza portatile perché praticamente tutte le macchine tradizionali sono complemento a due con regole di conversione in base all'estensione segno e bit di troncamento, compreso tra il sottoscritto e senza segno.

Tranne che l'estensione del segno non si verifica quando un valore viene ampliato durante la conversione da non firmato a firmato. Quindi un problema è la rara situazione in cui int è più largo di size_t.Se un risultato a 16 bit size_t è 65533, a causa della sottrazione di 4 da 1, questo non produrrà un valore -3 quando convertito in un 32 bit int; produrrà 65533!

+0

Forse intendete" dipendente dall'implementazione "piuttosto che" definito dall'implementazione "? Quest'ultimo termine ha un significato formale assegnato dallo standard C++. –

+0

Inoltre, il tuo ultimo paragrafo ha dimenticato di considerare le promozioni integrali che vengono eseguite prima che avvenga la sottrazione. –

+1

@BenVoigt Right; ma questo sarà raro. Com'è comune che 'size_t' corrisponda a' unsigned short' o 'unsigned char'? Ancora, punto valido. Per quanto riguarda il "risultato definito dall'implementazione", quell'uso è radicato in me dall'ISO C. Tutto in questa risposta si applica a C e C++. – Kaz