Faut-il toujours utiliser 'int' pour les nombres en C, même si elles sont non-négatives?
J'ai toujours utiliser unsigned int pour les valeurs qui ne doit jamais être négatif. Mais aujourd'hui, je
remarqué cette situation dans mon code:
void CreateRequestHeader( unsigned bitsAvailable, unsigned mandatoryDataSize,
unsigned optionalDataSize )
{
If ( bitsAvailable – mandatoryDataSize >= optionalDataSize ) {
//Optional data fits, so add it to the header.
}
//BUG! The above includes the optional part even if
//mandatoryDataSize > bitsAvailable.
}
Devrais-je commencer à utiliser int au lieu de unsigned int pour les chiffres, même s'ils
ne peut pas être négatif?
- Quel est le problème: si (bitsAvailable >= optionalDataSize + mandatoryDataSize) { ... } ?
- Pour info, Java ne supporte pas les types non signés, donc si jamais vous prévoyez d'avoir votre code d'interopérabilité avec Java, vous devriez éviter ces types, à moins que vous vraiment besoin de la plage du type de valeurs spécifiques. Je ne crois qu'il est approprié d'utiliser unsigned uniquement pour les fins de l'indication que les valeurs négatives ne sont pas pris en charge/autorisés.
- Un autre FYI: ce genre de bugs sont le genre que un bon analyseur de code statique trouverez pour vous. Coverity trouverez des problèmes comme celui-ci, n'avez pas utilisé d'autres assez pour le dire, mais je suis sûr que la plupart d'entre eux serait prise que. Voici une liste des outils disponibles: en.wikipedia.org/wiki/List_of_tools_for_static_code_analysis
- Voir aussi: stackoverflow.com/questions/1951519/when-to-use-stdsize-t pour le C++, mais les réponses restent pour la plupart s'appliquent
- Il n'est pas parfait non plus. Plus peut provoquer un débordement, et l'envelopper non signé.
Vous devez vous connecter pour publier un commentaire.
La réponse à "dois-je toujours ..." est presque certainement "non", il y a beaucoup de facteurs qui déterminent si vous devez utiliser un type de données - la cohérence est importante.
Mais, c'est un très subjective de la question, il est vraiment facile de gâcher unsigneds:
résultats dans une boucle infinie.
C'est pourquoi certains guides de style, y compris Google C++ Guide de Style décourager
unsigned
types de données.Dans mon opinion personnelle, je n'ai pas rencontré de nombreux bugs causés par ces problèmes avec des unsigned types de données, je dirais, utilisez les assertions de vérifier votre code et de les utiliser judicieusement (et moins lorsque vous effectuez l'arithmétique).
unsigned
permet de détecter les erreurs lors de la compilation de la phase plutôt qu'au moment de l'exécution. Les valeurs ordinales, comme les quantités, doivent êtreunsigned int
plutôt quesigned int
.!= ~0
en tant que votre condition de fin - il est utile non signé non valide/valeur de fin. C'est une petite bidouille (0
est de type int, donc~0
est-1
) mais sur sane machines de l'implicite cast fonctionne, tout simplement, et visuellement c'est moins bizarre que d'avoir un unsigned-1
.signed
etunsigned
types, qui peuvent donner des silencieux et des résultats surprenants. Il n'y a pas trop de contraintes syntaxiques entre les deux, qui peuvent déclencher une compilation de panne (sauf si vous passez supplémentaires drapeaux d'avertissement du compilateur). L'avantage d'uneunsigned
type est surtout sémantique, sauf si vous êtes spécifiquement en utilisant le type non signé afin d'éviter la manipulation du bit de signe (par exemple dans un masque de bits).>=0
... c'est pourquoi il est devenu une "chasse aux sorcières' 🙂for (unsigned i = 10; i != -1; --i)
est parfaitement bien.int
doit être utilisé. Lorsque approprié, vous êtes invités à utiliser les types standard commesize_t
etptrdiff_t
." et puis, il va à élaborer, dans certains cas, vous devez utiliserint
au lieu de l'un des types non signés (commeuint32_t
). Peut-être Google guide de style a changé depuis cette réponse a été écrit?uint32_t
, sauf s'il existe une raison valable, comme la représentation d'une séquence de bits plutôt que d'un nombre, ou vous avez besoin défini débordement modulo 2^N. En particulier, n'utilisez pas les types non signés-à-dire un certain nombre ne sera jamais négatif. Au lieu de cela, utilisez les assertions pour cela.”Une chose qui n'a pas été mentionné, c'est que échange signé/non signé nombre peut conduire à des bogues de sécurité. C'est un grand problème, depuis de nombreuses fonctions dans la norme C-bibliothèque/retour unsigned numéros (fread, memcpy, malloc etc. tous prendre
size_t
paramètres)Par exemple, prenez la suite anodin exemple (à partir de code réel):
A l'air inoffensif, non? Le problème est que
length
est signé, mais est converti en unsigned lorsqu'il est passé àmemcpy
. Ainsi, le réglage de la longueur deSHRT_MIN
permettra de valider les<= 512
test, mais à causememcpy
de copier plus de 512 octets de la mémoire tampon, ce qui permet à un attaquant de remplacer la fonction de l'adresse de retour sur la pile et (après un peu de travail) prendre le contrôle de votre ordinateur!Vous pouvez naïvement dire, "Il est donc évident que la longueur doit être
size_t
ou vérifié pour être>= 0
, je ne pourrais jamais faire cette erreur". Sauf, je vous garantis que si vous avez déjà écrit quelque chose de non trivial, que vous avez. Les auteurs de Windows, Linux, BSD, Solaris, Firefox, OpenSSL, Safari, MS Paint, Internet Explorer, Google Picasa, Opéra, Flash, Open Office, Subversion, Apache, Python, PHP, Pidgin, Gimp, ... sur et sur et sur ... - et ce sont tous les lumineux personnes dont travail est de savoir de sécurité.En bref, toujours utiliser
size_t
pour les tailles.Homme, la programmation est dur.
unsigned
ne serait pas vous aider, votre fonction ferait un plaisir de les écrire àmyArray[0xFFFFFFFF]
.size_t
(ou, plus précisément, c'est la conversion implicite entre signed/unsigned numéros). Bien sûr, oublier de vérifier les limites est également un problème. J'ai changé l'exemple afin de rendre cela plus clair merci.unsigned
lorsque vous appelezmemcpy()
.size_t
?length
n'est pas la racine du problème ici. Bien sûr, vous pourriez avoir utilisé un type non signé commesize_t
, mais alors vous ne serait même pas en mesure de vérifier que la limite inférieure est non-négative. Grâce à la conversion implicite de règles, que des causes justes différentes bugs. Comment est-ce une amélioration?unsigned int length = 0xFFFFFFFF
est utilisé, alorsif (length <= 512)
permettra d'évaluer àfalse
.-Wconversion
ou plus précisément-Wsign-conversion
met en garde sur l'implicite de la conversion des signes et l'erreur devient visible instantanément. Le problème est le caché de conversion.Dans certains cas, vous devez utiliser unsigned integer types sont:
size_t
valeurs.Mais pour le général, l'arithmétique, la chose est, quand vous dites que quelque chose "ne peut pas être négatif," cela ne veut pas nécessairement dire que vous devez utiliser un type non signé. Parce que vous peut mettre une valeur négative dans un unsigned, c'est juste qu'il va devenir une très grande valeur quand vous allez sortir. Donc, si vous voulez dire que les valeurs négatives sont interdites, comme pour une base de la racine carrée de la fonction, alors vous êtes en indiquant une condition préalable de la fonction, et vous devriez le faire valoir. Et vous ne pouvez pas affirmer que ce qui ne peut l'être, est; vous avez besoin d'un moyen de tenir out-of-band valeurs de sorte que vous pouvez les tester (c'est le même genre de logique derrière
getchar()
retour d'unint
et paschar
.)En outre, le choix de l'signé-vs-non signé peut avoir des répercussions sur la performance, ainsi. Jetez un oeil à l' (fictive) de code ci-dessous:
Les deux
foo
's sont les mêmes, sauf pour le type de leurs paramètres. Mais, lorsqu'il est compilé avecc99 -fomit-frame-pointer -O2 -S
, vous obtenez:Vous pouvez voir que
foo_i()
est plus efficace quefoo_u()
. C'est parce que unsigned de dépassement de capacité arithmétique est défini par la norme pour "envelopper", de sorte(a + 69u)
peut très bien être plus petit quea
sia
est très grand, et donc il doit y avoir des code pour ce cas. D'autre part, signé de dépassement de capacité arithmétique est pas défini, donc GCC va aller de l'avant et d'assumer arithmétique signée n'est pas de débordement, et ainsi de(a + 69)
ne peut pas jamais être inférieure àa
. Le choix de types non signés sans discernement peut donc limiter l'impact direct sur les performances.Bjarne Stroustrup, créateur du langage C++, met en garde sur l'utilisation de types non signés dans son livre Le langage de programmation C++:
La réponse est Oui. Le "unsigned int type de C et de C++ n'est pas un "toujours entier positif", quel que soit le nom du type ressemble. Le comportement de C/C++ unsigned ints n'a pas de sens si vous essayez de lire le type "non négatif"... par exemple:
En effet, non signé nombre est très utile pour certains cas parce qu'ils sont des éléments de l'anneau des entiers-modulo-N", N étant une puissance de deux. Unsigned ints sont utiles lorsque vous souhaitez l'utiliser modulo-n arithmétique, ou comme des masques de bits; ils ne sont PAS utiles, car les quantités.
Malheureusement en C et C++ non signés ont également été utilisés pour représenter les non-négatif quantités pour être en mesure d'utiliser tous les 16 bits lorsque les entiers où ce petit... à ce moment, être en mesure d'utiliser 32k ou 64 ko a été considéré comme une grande différence. J'avais le classer comme un accident historique... vous ne devriez pas essayer de lire une logique, car il n'y a pas de logique.
Par la façon dont, à mon avis, c'était une erreur... si 32k ne sont pas assez puis très bientôt de 64 ko de ne pas être suffisant; abusant de la modulo entier juste parce que d'un bit supplémentaire à mon avis, est d'un coût trop élevé à payer. Bien sûr, il aurait été raisonnable de le faire si une bonne non-type négatif était présent ou défini... mais non signé sémantique est tout à fait inacceptable pour l'utiliser en tant que non-négatif.
Parfois, vous pouvez trouver qui a dit que non signé est bon parce qu'il "documents" que vous ne voulez valeurs non négatives... cependant que la documentation est de valeur que pour ceux qui ne le savent pas vraiment comment unsigned travaille pour le C ou le C++. Pour moi, voir un type non signé utilisée pour les valeurs non négatives signifie simplement que qui a écrit le code ne comprends pas la langue, sur cette partie.
Si vous comprenez vraiment et souhaitez le "wrapping" comportement des entiers non signés alors qu'ils sont le bon choix (par exemple, j'ai presque toujours utiliser "unsigned char" quand je suis à la manipulation d'octets); si vous n'allez pas utiliser l'emballage comportement (et que le comportement est juste va être un problème pour vous, comme dans le cas de la différence vous le montre), alors c'est un indicateur clair que le type non signé est un mauvais choix et vous devez vous en tenir à la plaine ints.
Cela veut dire que le C++
std::vector<>::size()
type de retour est un mauvais choix ? Oui... c'est une erreur. Mais si vous le dites alors soyez prêt à être appelé mauvais noms par ceux qui ne comprennent pas que le "non signé" le nom est juste un nom... ce qu'il compte c'est le comportement et qui est une "modulo n" comportement (et ne serait d'envisager un "modulo n" type de la taille d'un conteneur à un choix judicieux).unsigned
's sémantique est illogique.tcp->stuffed - tcp->acked
et de savoir combien d'octets ont été placés dans le tampon mais pas reconnu même si les numéros de séquence avoir enroulé autour. Le problème est que les valeurs non signées ne sont pas réguliers habillage sémantique...int
genre à avoir des objets individuels qui ont été plus importants que 32K, mais traiter efficacement les objets de plus de 64 KO, il aurait fallu une plus grandeint
type. Le problème avecunsigned int
est que comme vous l'avez très justement remarque il est utilisé pour servir de deux disjoints rôles (les numéros rapport algébrique des anneaux). Je souhaite C permettrait d'ajouter de nouveaux types distincts pour les nombres naturels jusqu'à 2^2^n-1 [par exemple 65535], les nombres naturels jusqu'à 2^(2^n-1)-1 [par exemple, 32767], et algébrique anneaux mod 2^2^n [par exemple, 65536], sémantique, qui sont mieux dans chaque cas.Je semble être en désaccord avec la plupart des gens ici, mais je trouve
unsigned
types très utile, mais pas dans leur raw historique de la forme.Si vous, par conséquent, le bâton à la sémantique qu'un type que représente pour vous, il n'y a pas de problème: l'utilisation
size_t
(non signé) pour les indices de tableau, les données des décalages etc.off_t
(signé) pour les décalages de fichier. Utilisationptrdiff_t
(signé) pour les différences de pointeurs. Utilisationuint8_t
pour les petits entiers non signés etint8_t
pour signé une. Et vous éviter d'au moins 80% des problèmes de portabilité.Et n'utilisez pas
int
,long
,unsigned
,char
si vous ne devez pas. Ils appartiennent dans les livres d'histoire. (Vous devez parfois, l'erreur revient, champs de bits, e.g)Et pour en revenir à votre exemple:
bitsAvailable – mandatoryDataSize >= optionalDataSize
peut facilement être réécrit comme
bitsAvailable >= optionalDataSize + mandatoryDataSize
qui n'est pas d'éviter le problème d'un éventuel dépassement de capacité (
assert
est votre ami), mais vous permet de vous rapprocher un peu plus de l'idée de ce que vous voulez tester, je pense.uint16_t x = 0xFFFF; uint16_t y=x*x;
quelle est la norme de dire à propos de la valeur dey
?int
. Le résultat de la multiplication semble être0xFFFE0001
ainsi la multiplication des débordements et le comportement est indéfini. C'est un bon exemple pourquoi on ne devrait jamais utiliser étroit types pour l'arithmétique. Lors de l'utilisation desize_t
ce problème ne se produit pas.int
. Si l'on était de l'écriture de code sur un tel système de mise à jour de 16 bits en complément à deux ou un complément de la somme de contrôle après avoir écrit une répétition de la valeur à un flux, de la multiplication de deuxuint16_t
serait le moyen naturel de le faire. De plus, jusqu'à très récemment, 99,9% des compilateurs C pour les systèmes 32 bits donnerait exactement le même calcul sans aucune difficulté. Alors que certains soutiennent que l'expression serait mieux rédigé comme1u*x*x
, je considère la nécessité pour ce dernier forme comme une lacune dans la langue spec.size_t
etptrdiff_t
vraiment aider beaucoup, donnéchar foo[100], *p1 = foo, *p2 = foo+100;
quelle est la valeur de(p1-p2) > sizeof foo
? Sur certains systèmes, où la plus grande taille de l'élément est entre 32768 et 65535, ou entre 2147483648 et 4294967295, l'expression de rendement 0, mais sur de nombreux autres systèmes, il serait de rendement 1.p2 - p1
?p2 - p1
n'est définie que si les deux pointeurs point à l'intérieur (ou l'un au-delà) le même objet. Donc, par définition, la valeur s'inscrit danssize_t
. Maintenant, si vous voulait vraiment direp1-p2
, le type de résultat estptrdiff_t
c'est donc une valeur négative. Si il underflows, là encore, le comportement n'est pas défini. --- Je ne suis pas encore clair ce que vous voulez prouver ou de réfuter. La seule chose que vous avez montré jusqu'à présent, certains miscomfort avec ce que je dis dans ma réponse.wrap16_t x=65535; wrap16_t y=x*x;
être écrit clairement définie par la sémantique, à la différence deint16_t
qui nécessiterait la dernière expression à l'écrit comme 1uxx. A lot of code today expects
uint32_t` va se comporter comme unwrap32_t
et échouait sur une machine oùint
est de 64 bits, mais si unwrap32_t
type existait un tel code pourrait être porté facilement sur les systèmes 64 bits en modifiant certainsuint32_t
àwrap32_t
et certains denat32_t
; dans la plupart des cas, il devrait être assez évident, ce qui serait nécessaire.char
est signé ou non signé, si des choses comme dépassement d'entier va se comporter de manière totalement prévisible, un peu prévisible, ou de nier les lois du temps et de la causalité, etc. mais il n'y a pas de manière standard pour les programmes afin de préciser les exigences.Sans Bug, tant que mandatoryDataSize + optionalDataSize ne peut pas déborder le type entier non signé -- la dénomination de ces variables, m'amène à croire que c'est probablement le cas.
Vous ne pouvez pas éviter complètement unsigned types de code portable, parce que beaucoup de typedefs dans la bibliothèque standard ne sont pas signés (notamment
size_t
), et de nombreuses fonctions de retour de personnes (par exemple,std::vector<>::size()
).Cela dit, en général, je préfèrent s'en tenir aux types signés dans la mesure du possible, pour les raisons que vous avez exposées. Ce n'est pas seulement le cas où vous mettre dans le cas de mixte signé/non signé de l'arithmétique, de la signé argument est tranquillement promu non signé.
À partir des commentaires sur l'un de Eric Lipperts billets de Blog (Voir ici):
Jeffrey L. Whitledge
Eric Lippert
size_t
). Ce n'est pas le cas .Net, où les dépassements de mémoire tampon sont un non-problème.La situation où
(bitsAvailable – mandatoryDataSize)
produit un "inattendue" résultat si les types ne sont pas signés etbitsAvailable < mandatoryDataSize
la raison est que, parfois, signé types sont utilisés même lorsque les données devrait jamais être négatif.Je pense qu'il n'y a aucune règle dure et rapide - en général, je "par défaut" à l'aide de unsigned types de données qui n'a pas de raison d'être négatif, mais alors vous avez à prendre pour s'assurer que l'arithmétique d'emballage ne pas exposer à des bogues.
Et puis, si vous utilisez des types signés, vous avez encore parfois à considérer overflow:
La clé est que vous devez prendre soin lors de l'exécution de l'arithmétique pour ces sortes de bugs.
size_t
(ou même un peu plus grand type non signé), mais vous pouvez toujours besoin de gérer emballage des erreurs.Non, vous devez utiliser le type qui est bon pour votre demande. Il n'y a pas de règle d'or. Parfois sur de petits microcontrôleurs il est par exemple plus rapide et efficace en terme de mémoire à utiliser-dire 8 ou 16 bits variables dans la mesure du possible, comme c'est souvent le natif du chemin de données de taille, mais c'est un cas très spécial. Je recommande également à l'aide de stdint.h dans la mesure du possible. Si vous utilisez visual studio, vous pouvez trouver des versions sous licence BSD.
Si il y a une possibilité de dépassement de capacité, puis affectez les valeurs pour le meilleur type de données en cours de calcul, c'est à dire:
Sinon, il suffit de vérifier les valeurs individuellement au lieu de calculer:
Vous aurez besoin de regarder les résultats des opérations que vous effectuez sur les variables pour vérifier si vous pouvez obtenir plus de/underflows - dans votre cas, le résultat étant potentiellement négatifs. Dans ce cas, vous êtes mieux d'utiliser l'signé équivalents.
Je ne sais pas si c'est possible en c, mais dans ce cas, je voudrais juste lancer la X-Y chose à un int.
Si vos numéros de devrait jamais être inférieur à zéro, mais ont une chance d'être < 0, par tous les moyens d'utiliser les entiers signés et saupoudrer des assertions ou des autres d'exécution des contrôles autour de. Si vous êtes vraiment en train de travailler avec 32 bits (ou 64, ou 16, selon votre architecture cible) valeurs où le bit le plus significatif signifie autre chose que "-", vous ne devriez utiliser unsigned variables pour les tenir. Il est plus facile de détecter les débordements d'entiers où un nombre qui doit toujours être positive est très négatif que quand c'est nul, donc si vous n'avez pas besoin que peu, aller avec le signé une.
Supposons que vous avez besoin de compter de 1 à 50000. Vous pouvez le faire avec deux octets entier non signé, mais pas avec un deux-octet entier signé (si l'espace de questions que beaucoup).