2009-06-15 11 views
8

Sto provando a scrivere una funzione di template C++ che genererà un'eccezione di runtime su overflow di interi in cast tra diversi tipi di integrale, con diverse larghezze e possibili mancate corrispondenze firmate/non firmate. Per questi scopi non mi occupo di eseguire il cast da tipi a virgola mobile a tipi interi, né altre conversioni da oggetto a oggetto. Mi piacerebbe farlo senza dover scrivere un sacco di codice caso speciale. Questo è quello che attualmente ho:C++ Template per interi integer sicuri

template< typename T, typename R > void safe_cast(const T& source, R& result) 
{ 
    // get the maximum safe value of type R 
    R rMax = (R) ~0; 
    if (rMax < 0) // R is a signed type 
    { 
     // assume that we're on an 8-bit twos-compliment machine 
     rMax = ~(0x80 << ((sizeof(R) - 1) * 8)); 
    } 

    if ((source & rMax ) != source) 
    { 
     throw new IntegerOverflowException(source); 
    } 

    result = static_cast<R>(source); 
} 

È corretto ed efficace?

MODIFICA: Per vari motivi, stl non è disponibile, quindi non posso usare std :: numeric_limits, e qualsiasi cosa di Boost è giusta.

+0

Ma è possibile copiare il codice necessario da numeric_limits nel proprio helper di modello. Assegna tutto a uint64 (o qualunque sia la dimensione massima consentita) e fai confronti all'interno di quel tipo. –

+1

Potrebbe funzionare, ma è necessario conoscere i termini della licenza quando si copia codice come questo. Oltre a potenzialmente violare i termini, si potrebbe inavvertitamente "infettare" il loro codice, come nel caso della GPL. Assicurati che entrambe le licenze siano compatibili prima di fare questo genere di cose. Si applica la solita dichiarazione di non responsabilità "I am not a lawyer". – Void

+0

Quali sono i vari motivi per cui non è possibile utilizzare l'STL? – GManNickG

risposta

5

Hai provato SafeInt? È un modello multipiattaforma che eseguirà controlli di overflow di numeri interi per una varietà di tipi di interi. E 'disponibile su CodePlex

12

È possibile ottenere i valori minimi e massimi sicuri (e molte altre informazioni) per qualsiasi tipo fondamentale in un modo molto più elegante utilizzando il modello std::numeric_limits, ad es. std::numeric_limits<T>::max(). Dovrai includere <limits>.

Riferimento: http://www.cplusplus.com/reference/std/limits/numeric_limits/

+0

questa modifica 7 anni più tardi mi ha fatto cambiare il mio upvote a un downvote. Perché l'esempio presentato aggiunto da @jpo38 non funziona. Esempio di coppia: Da = int, A = non firmato. fonte == - 1. –

+0

In realtà, l'ho visto e risolto nel mio codice da quando ho modificato questo post. Ma ho dimenticato di aggiornare la modifica ... mi dispiace. Ora getto se 'static_cast (sorgente))! = Source' (in pratica, se alcune informazioni sono state perse dal cast ... funziona molto bene, in qualche modo simile a ciò che Tim propone di seguito. desiderio non riesce quando si passa da firmato a non firmato e il modo intorno. – jpo38

11

è spinta un'opzione? Se è così, prova boost::numeric_cast<>. Sembra fornire le caratteristiche che stai cercando.

1

Ho ragione nel ritenere che nel caso in cui R è firmato che si sta tentando di riempire Rmax con tutti 1s tranne l'ultimo bit? Se questo è il caso, allora dovresti avere 0x80 (1000 0000) invece di 0x10 (0001 0000).

Inoltre, non sembra che la funzione supporti i numeri negativi per la sorgente.

Edit:

Ecco una versione leggermente modificata che ho testato per la conversione da interi a caratteri:

template< typename T, typename R > 
void safe_cast(const T& source, R& result) 
{ 
    // get the maximum safe value of type R 
    R rMax = (R) ~0; 
    if (rMax < 0) // R is a signed type 
    { 
     // assume that we're on an 8-bit twos-compliment machine 
    rMax = (0x80 << ((sizeof(R) - 1) * 8)); 
    if(source >= 0) 
     rMax = ~rMax; 
    } 

    if ((source >= 0 && (source & rMax ) != source) || (source < 0 && (source & rMax) != rMax)) 
    { 
     throw new IntegerOverflowException(source); 
    } 

    result = static_cast<R>(source); 
} 

Edit: Errore fisso.

7

Penso che questi lavori ora, indipendentemente dal fatto che si utilizza il complemento a due o meno. Si prega di testare ampiamente prima di usarlo. Danno i seguenti risultati. Ogni riga fornisce un errore di asserzione (basta modificarli in eccezioni a piacere)

/* unsigned -> signed, overflow */ 
safe_cast<short>(UINT_MAX); 

/* unsigned -> unsigned, overflow */ 
safe_cast<unsigned char>(ULONG_MAX); 

/* signed -> unsigned, overflow */ 
safe_cast<unsigned long>(-1); 

/* signed -> signed, overflow */ 
safe_cast<signed char>(INT_MAX); 

/* always works (no check done) */ 
safe_cast<long>(INT_MAX); 

// giving these assertion failures results 
(type)f <= (type)is_signed<To>::v_max 
f <= (To)-1 
f >= 0 
f >= is_signed<To>::v_min && f <= is_signed<To>::v_max 

Implementazione. Innanzitutto alcune utilità per verificare la presenza di ranghi interi (i tipi con gradi più alti saranno in grado di contenere valori di tipi con grado inferiore, dato lo stesso segno. E alcuni strumenti di promozione, per essere in grado di capire un tipo comune e sicuro (questo non sarà mai produrre un tipo firmate se un tipo senza segno è coinvolto, se il tipo firmata non sarà in grado di memorizzare tutti i valori di quello non firmato).

/* ranks */ 
template<typename> struct int_rank; 
#define RANK(T, I) template<> struct int_rank<T> \ 
    { static int const value = I; } 

RANK(char, 1); RANK(unsigned char, 1); RANK(signed char, 1); 
RANK(short, 2); RANK(unsigned short, 2); 
RANK(int, 3); RANK(unsigned int, 3); 
RANK(long, 4); RANK(unsigned long, 4); 
#undef RANK 

/* usual arith. conversions for ints (pre-condition: A, B differ) */ 
template<int> struct uac_at; 
template<> struct uac_at<1> { typedef int type; }; 
template<> struct uac_at<2> { typedef unsigned int type; }; 
template<> struct uac_at<3> { typedef long type; }; 
template<> struct uac_at<4> { typedef unsigned long type; }; 

template<typename A, typename B> 
struct uac_type { 
    static char (&f(int))[1]; 
    static char (&f(unsigned int))[2]; 
    static char (&f(long))[3]; 
    static char (&f(unsigned long))[4]; 
    typedef typename uac_at<sizeof f(0 ? A() : B())>::type type; 
}; 

/* signed games */ 
template<typename> struct is_signed { static bool const value = false; }; 
#define SG(X, TT) template<> struct is_signed<X> { \ 
    static bool const value = true;    \ 
    static X const v_min = TT##_MIN;    \ 
    static X const v_max = TT##_MAX;    \ 
} 

SG(signed char, SCHAR); 
SG(short, SHRT); 
SG(int, INT); 
SG(long, LONG); 
#undef SG 

template<> struct is_signed<char> { 
    static bool const value = (CHAR_MIN < 0); 
    static char const v_min = CHAR_MIN; // just in case it's signed... 
    static char const v_max = CHAR_MAX; 
}; 

I modelli di conversione fanno uso di loro, per capire per ciascun caso in cui ciò che deve essere fatto o non fatto.

template<typename To, typename From, 
     bool to_signed = is_signed<To>::value, 
     bool from_signed = is_signed<From>::value, 
     bool rank_fine = (int_rank<To>::value >= int_rank<From>::value)> 
struct do_conv; 

/* these conversions never overflow, like int -> int, 
* or int -> long. */ 
template<typename To, typename From, bool Sign> 
struct do_conv<To, From, Sign, Sign, true> { 
    static To call(From f) { 
     return (To)f; 
    } 
}; 

template<typename To, typename From> 
struct do_conv<To, From, false, false, false> { 
    static To call(From f) { 
     assert(f <= (To)-1); 
     return (To)f; 
    } 
}; 

template<typename To, typename From> 
struct do_conv<To, From, false, true, true> { 
    typedef typename uac_type<To, From>::type type; 
    static To call(From f) { 
     /* no need to check whether To's positive range will 
     * store From's positive range: Because the rank is 
     * fine, and To is unsigned. 
     * Fixes GCC warning "comparison is always true" */ 
     assert(f >= 0); 
     return (To)f; 
    } 
}; 

template<typename To, typename From> 
struct do_conv<To, From, false, true, false> { 
    typedef typename uac_type<To, From>::type type; 
    static To call(From f) { 
     assert(f >= 0 && (type)f <= (type)(To)-1); 
     return (To)f; 
    } 
}; 

template<typename To, typename From, bool Rank> 
struct do_conv<To, From, true, false, Rank> { 
    typedef typename uac_type<To, From>::type type; 
    static To call(From f) { 
     assert((type)f <= (type)is_signed<To>::v_max); 
     return (To)f; 
    } 
}; 

template<typename To, typename From> 
struct do_conv<To, From, true, true, false> { 
    static To call(From f) { 
     assert(f >= is_signed<To>::v_min && f <= is_signed<To>::v_max); 
     return (To)f; 
    } 
}; 

template<typename To, typename From> 
To safe_cast(From f) { return do_conv<To, From>::call(f); } 
3

ne dite:

template< typename T, typename R > void safe_cast(const T& source, R& result) 
{ 
    R temp = static_cast<R>(source); 
    if (static_cast<T> (temp) != source 
     || (temp < 0 && source > 0) 
     || (temp > 0 && source < 0)) 
    { 
     throw IntegerOverflowException(source); 
    } 
    result = temp; 
} 

Poi si sta solo controllando se il casting ha funzionato. Assicurati di recuperare ciò che hai iniziato e che il segno non si è invertito.

EDIT: Dal momento che il commento qui sotto ottenuto incasinato, eccolo, formattato:

int myint (-1); 
safe_cast(myint, mychar); 
safe_cast(mychar, myuchar); // Exception is thrown here 
safe_cast(myuchar, myint); 

Il cast da int a char funziona bene. Il cast dal char al char unsigned genera un'eccezione (come dovrebbe). Non vedo un problema qui.

+0

o, Il compilatore sputerà gli avvertimenti quando uno dei due tipi è senza segno e l'altro non lo è (il confronto è sempre falso a causa della gamma limitata di tipi di dati in g ++) quando confrontando con 0, ma non verrà buttato e i dati andranno persi: Se usi il tuo cast da -1 (int) a -1 (char) a 0xFF (char unsigned) torna a int non otterrai -1. il cast non è "sicuro" poiché i valori vengono modificati nel modo. –

+0

ah, l'ho provato con un compilatore diverso. Gli avvisi si riferiscono a "temp <0" quando temp non è firmato, che è ok Non gettare a questo punto e nessun dato è perso. Ho provato cosa suggerisci, ad esempio int myint (-1); safe_cast (myint, mychar); safe_cast (mychar, myuchar); // L'eccezione viene lanciata qui safe_cast (myuchar, myint); Il cast da int a char funziona correttamente. Il cast dal char al char unsigned genera un'eccezione (come dovrebbe). Non vedo un problema qui. – Tim

0

Devo mancare qualcosa, ma non è questo che vuoi ?:

// using a more cast-like prototype, if I may: 
template<class to, class from> inline 
to safe_cast(from f) 
{ 
    to t = static_cast<to>(f); 
    if (t != f) throw whatever; // no new! 
    return t; 
} 
+0

No, se usi il cast da -1 (int) a -1 (char) a 0xFF (char unsigned) di nuovo a int non otterrai -1. Il cast non è "sicuro" in quanto i valori vengono modificati nel modo. –

+0

Ciao Dribeas, Mi dispiace, non sono sicuro di quello che stai dicendo. . safe_cast (int (-1)) non ha un overflow e restituisce il valore . safe_cast (char (-1)) cambia il segno (e il valore) e genera. quindi il comportamento corretto. o, cosa stai dicendo? – sly

+0

assumendo che il carattere sia firmato, safe_cast (char (-1)) verrà impostato su UCHAR_MAX (probabilmente 255). allora if (t! = f) promuoverà char a int, cedendo a -1 e unsigned char a int, ottenendo 255, quindi non sono uguali. MA facendo safe_cast (- 1), verrà impostato su UINT_MAX, quindi if non promuoverà nulla e convertirà int a unsigned int (UAC), restituendo UINT_MAX e pensando erroneamente al cast riuscito. –

0

Ho un singolo colpo di testa sweet.hpp chiamato conv.hpp. Metterà alla prova i limiti per tutti i tipi di interi e inoltre consente di e da string cast per intero.

short a = to<short>(1337); 
std::string b = to<std::string>(a); 
long c = to<long>(b); 
0

considerano numerici Cassaforte alla http://rrsd.com/blincubator.com/bi_library/safe-numerics

Questa libreria fornisce drop-in sostituzioni per tutti i tipi primitivi interi C. Le operazioni C che risultano risultati errati - inclusa la fusione vengono intrappolate quando rilevate.