2009-06-15 6 views
8

J'essaye d'écrire une fonction de modèle C++ qui lancera une exception d'exécution lors d'un débordement d'entier en moulage entre différents types entiers, avec des largeurs différentes, et une discordance signée/non signée possible. À ces fins, je ne m'intéresse pas à la conversion de types à virgule flottante en types entiers, ni à d'autres conversions d'objet à objet. Je voudrais le faire sans avoir à écrire beaucoup de code de cas spécial. C'est ce que j'ai actuellement:Modèle C++ pour les entiers entiers sûrs

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

Est-ce correct et efficace?

EDIT: Pour diverses raisons, stl n'est pas disponible, donc je ne peux pas utiliser std :: numeric_limits, et tout ce qui vient de Boost est juste sorti.

+0

Mais vous pouvez copier le code dont vous avez besoin de numeric_limits dans votre propre assistant. Affectez tout à uint64 (ou quelle que soit la taille maximale autorisée) et faites des comparaisons dans ce type. –

+1

Cela peut fonctionner, mais il faut vraiment être conscient des termes de licence lors de la copie de code comme celui-ci. En plus de violer potentiellement les termes, on pourrait involontairement "infecter" leur code, comme c'est le cas avec la GPL. Assurez-vous que les deux licences sont compatibles avant de faire ce genre de chose. L'avertissement habituel "Je ne suis pas un avocat" s'applique. – Void

+0

Quelles sont les différentes raisons pour lesquelles vous ne pouvez pas utiliser la STL? – GManNickG

Répondre

5

Avez-vous essayé SafeInt? C'est un modèle multi-plateforme qui fera des vérifications de débordement d'entier pour une variété de types entiers. Il est disponible sur CodePlex

12

Vous pouvez obtenir les valeurs de sécurité minimale et maximale (et beaucoup d'autres informations) pour n'importe quel type fondamental d'une manière beaucoup plus élégante en utilisant le modèle std::numeric_limits, par ex. std::numeric_limits<T>::max(). Vous devrez inclure <limits>.

Référence: http://www.cplusplus.com/reference/std/limits/numeric_limits/

+0

cette édition 7 ans plus tard m'a fait changer mon upvote à une downvote. Parce que l'exemple présenté ajouté par @ jpo38 ne fonctionne pas. Exemple de paire: De = int, To = unsigned. source == - 1. –

+0

En fait, j'ai vu ça et j'ai corrigé ça dans mon code depuis que j'ai édité ce post. Mais j'ai oublié de mettre à jour l'édition ... désolé. Je lance maintenant si 'static_cast (static_cast (source))! = Source' (fondamentalement, si certaines informations ont été perdues par le casting ... cela fonctionne très bien.Tout comme similaire à ce que Tim propose ci-dessous.Pas de référence à max/min – jpo38

11

est de stimuler une option? Si oui, essayez boost::numeric_cast<>. Il semble fournir les caractéristiques que vous recherchez.

1

Suis-je raison de supposer que dans le cas où R est signé, vous essayez de remplir RMax avec tous les 1 sauf pour le dernier bit? Si c'est le cas, alors vous devriez avoir 0x80 (1000 0000) au lieu de 0x10 (0001 0000).

De même, il ne semble pas que votre fonction prenne en charge les nombres négatifs pour la source.

Edit:

Voici une version légèrement modifiée que je l'ai testé pour convertir ints à caractères:

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: Correction d'une erreur.

7

Je pense que cela fonctionne maintenant, que vous utilisiez ou non le complément à deux. S'il vous plaît tester intensivement avant de l'utiliser. Ils donnent les résultats suivants. Chaque ligne donne un échec d'assertion (il suffit de les changer en exceptions à votre guise)

/* 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 

Implémentation. D'abord quelques utilitaires pour vérifier les rangs entiers (les types avec des rangs plus élevés pourront contenir des valeurs de types avec un rang inférieur, avec le même signe.) Et quelques outils de promotion, pour pouvoir trouver un type commun et sûr (cela ne sera jamais donner un type signé si un type non signé est impliqué, si le type signé ne sera pas en mesure de stocker toutes les valeurs de celui non signé).

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

les modèles de conversion font usage, à savoir pour chaque cas quand ce qui doit être fait ou pas fait.

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

Que diriez-vous:

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

Ensuite, vous êtes en train de vérifier si la coulée a travaillé. Assurez-vous de retrouver ce que vous avez commencé, et que le signe n'a pas basculé.

EDIT: Depuis le commentaire ci-dessous se est foiré, il est ici, formaté:

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

Le casting de int à charbon fonctionne très bien. La conversion de char en char non signé déclenche une exception (comme il se doit). Je ne vois pas de problème ici.

+0

o, Le compilateur crachera des avertissements quand l'un des deux types est non signé et l'autre non (la comparaison est toujours fausse en raison de la portée limitée du type de données en g ++) quand en comparant avec 0, mais il ne lancera pas et les données seront perdues: Si vous utilisez votre distribution de -1 (int) à -1 (char) à 0xFF (unsigned char) retour à int, vous n'obtiendrez pas -1. le cast n'est pas 'sûr' car les valeurs sont changées sur le chemin –

+0

ah, j'ai testé avec un compilateur différent Les avertissements se réfèrent à "temp <0" quand la température est non signée, ce qui est correct Je ne jette pas à ce stade, et aucune donnée n'est perdue. J'ai testé ce que vous suggérez, c'est-à-dire: int myint (-1); safe_cast (myint, mychar); safe_cast (mychar, myuchar); // Une exception est levée ici safe_cast (myuchar, myint); La conversion de int en char fonctionne très bien. La conversion de char en char non signé déclenche une exception (comme il se doit). Je ne vois pas de problème ici. – Tim

0

Je dois manquer quelque chose, mais est-ce pas ce que vous voulez ?:

// 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

Non, si vous utilisez votre distribution de -1 (int) à -1 (char) à 0xFF (unsigned char) retour à int vous n'obtiendrez pas -1. Le casting n'est pas 'sûr' car les valeurs sont modifiées dans le chemin. –

+0

Hi Dribeas, Je suis désolé, je ne suis pas sûr de ce que vous dites. . safe_cast (int (-1)) ne déborde pas, et renvoie bien . safe_cast (char (-1)) change de signe (et de valeur) et renvoie. d'où le bon comportement. ou, que dites-vous? – sly

+0

En supposant que char est signé, safe_cast (char (-1)) mettra t à UCHAR_MAX (probablement 255). alors si (t! = f) promouvra char à int, cédant à -1 et char non signé à int, produisant 255, ils ne sont donc pas égaux. MAIS faire safe_cast (- 1), il mettra à UINT_MAX, alors le if ne fera rien, et convertira l'int en unsigned int (UAC), renvoyant UINT_MAX, et pensant à tort que la distribution a réussi. –

0

J'ai un en-tête unique à sweet.hpp appelé conv.hpp. Il testera les limites pour tous les types d'entiers et autorisera aussi les conversions de chaînes de caractères entiers.

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

considèrent Numerics Coffre-fort à http://rrsd.com/blincubator.com/bi_library/safe-numerics

Cette bibliothèque fournit des remplacements de substitution pour tous les types entiers primitifs C. Les opérations C qui donnent des résultats erronés, y compris la coulée, sont piégées lorsqu'elles sont détectées.

Questions connexes