Je regardais cette vidéo . Bjarne Stroustrup dit que les entiers non signés sont sujets aux erreurs et entraînent des bugs. Vous ne devez donc les utiliser que lorsque vous en avez vraiment besoin. J'ai également lu dans l'une des questions sur Stack Overflow (mais je ne me souviens pas laquelle) que l'utilisation d'entiers non signés peut entraîner des bogues de sécurité .
Comment conduisent-ils à des bugs de sécurité? Quelqu'un peut-il l'expliquer clairement en donnant un exemple approprié?
Un aspect possible est que les entiers non signés peuvent entraîner des problèmes quelque peu difficiles à repérer dans les boucles, car le dépassement de capacité conduit à de grands nombres. Je ne peux pas compter (même avec un entier non signé!) Combien de fois j'ai fait une variante de ce bug
for(size_t i = foo.size(); i >= 0; --i)
...
Notez que, par définition, i >= 0
Est toujours vrai. (Ce qui provoque cela en premier lieu, c'est que si i
est signé, le compilateur avertira d'un éventuel débordement avec le size_t
De size()
).
Il y a d'autres raisons mentionnées Danger - types non signés utilisés ici! , dont la plus forte, à mon avis, est la conversion de type implicite entre signé et non signé.
Un facteur important est qu'il rend la logique de boucle plus difficile: imaginez que vous voulez itérer sur tout sauf le dernier élément d'un tableau (ce qui se produit dans le monde réel). Vous écrivez donc votre fonction:
void fun (const std::vector<int> &vec) {
for (std::size_t i = 0; i < vec.size() - 1; ++i)
do_something(vec[i]);
}
Ça a l'air bien, non? Il compile même proprement avec des niveaux d'avertissement très élevés! ( Live ) Donc, vous mettez cela dans votre code, tous les tests se déroulent bien et vous l'oubliez.
Maintenant, plus tard, quelqu'un arrive et passe un vector
vide à votre fonction. Maintenant, avec un entier signé, vous auriez, espérons-le, remarqué le avertissement du compilateur de comparaison de signes , introduit la distribution appropriée et n'avez pas publié le code du buggy en premier lieu.
Mais dans votre implémentation avec l'entier non signé, vous encapsulez et la condition de boucle devient i < SIZE_T_MAX
. Catastrophe, UB et crash le plus probable!
Je veux savoir comment ils conduisent à des bugs de sécurité?
C'est aussi un problème de sécurité, en particulier c'est un buffer overflow . Une façon d'exploiter éventuellement cela serait si do_something
ferait quelque chose qui peut être observé par l'attaquant. Il pourrait peut-être trouver quelle entrée est entrée dans do_something
, et de cette façon, les données auxquelles l'attaquant ne devrait pas pouvoir accéder seraient divulguées de votre mémoire. Ce serait un scénario similaire au bug Heartbleed . (Merci à un monstre à cliquet de l'avoir souligné dans son commentaire .)
Je ne vais pas regarder une vidéo juste pour répondre à une question, mais un problème est les conversions déroutantes qui peuvent se produire si vous mélangez des valeurs signées et non signées. Par exemple:
#include <iostream>
int main() {
unsigned n = 42;
int i = -42;
if (i < n) {
std::cout << "All is well\n";
} else {
std::cout << "ARITHMETIC IS BROKEN!\n";
}
}
Les règles de promotion signifient que i
est converti en unsigned
pour la comparaison, donnant un grand nombre positif et un résultat surprenant.
Le gros problème avec un entier non signé est que si vous soustrayez 1 d'un entier non signé 0, le résultat n'est pas un nombre négatif, le résultat n'est pas inférieur au nombre avec lequel vous avez commencé, mais le résultat est la plus grande valeur entière non signée possible .
unsigned int x = 0;
unsigned int y = x - 1;
if (y > x) printf ("What a surprise! \n");
Et c'est ce qui rend les erreurs int non signées sujettes. Bien sûr, un entier non signé fonctionne exactement comme il est conçu pour fonctionner. C'est absolument sûr si vous savez ce que vous faites et ne faites aucune erreur. Mais la plupart des gens font des erreurs.
Si vous utilisez un bon compilateur, vous activez tous les avertissements générés par le compilateur et il vous avertira lorsque vous effectuez des choses dangereuses susceptibles d’être des erreurs.
Le problème avec les types entiers non signés est qu'en fonction de leur taille, ils peuvent représenter deux choses différentes:
int
(par exemple uint8
) hold nombres dans la plage 0..2ⁿ-1, et les calculs avec eux se comporteront selon les règles de l'arithmétique des nombres entiers à condition qu'ils ne dépassent pas la plage de int
type. Selon les règles actuelles, si un tel calcul dépasse la plage d'un int
, un compilateur est autorisé à faire tout ce qu'il veut avec le code, allant même jusqu'à nier les lois du temps et de la causalité (certains compilateurs faites exactement cela!), et même si le résultat du calcul est affecté à un type non signé plus petit que int
.unsigned int
et des membres de maintien plus grands de l'anneau algébrique enveloppant abstrait d'entiers mod 2ⁿ congruent; cela signifie effectivement que si un calcul sort de la plage 0..2ⁿ-1, le système ajoutera ou soustraira le multiple de 2ⁿ qui serait nécessaire pour remettre la valeur dans la plage.Par conséquent, étant donné uint32_t x=1, y=2;
l'expression x-y
peut avoir l'une des deux significations selon que int
est supérieur à 32 bits.
int
est supérieur à 32 bits, l'expression soustrait le nombre 2 du nombre 1, ce qui donne le nombre -1. Notez que bien qu'une variable de type uint32_t
ne peut pas contenir la valeur -1 quelle que soit la taille de int
, et le stockage de -1 entraînerait une telle variable pour contenir 0xFFFFFFFF, mais à moins ou jusqu'à ce que la valeur soit forcée à un type non signé, il le fera se comportent comme la quantité signée -1.int
est de 32 bits ou moins, l'expression donnera un uint32_t
valeur qui, ajoutée à uint32_t
valeur 2, donnera uint32_t
valeur 1 (c'est-à-dire le uint32_t
valeur 0xFFFFFFFF).À mon humble avis, ce problème pourrait être résolu proprement si C et C++ devaient définir de nouveaux types non signés [par ex. unum32_t et uwrap32_t] de telle sorte qu'un unum32_t
se comporterait toujours comme un nombre, quelle que soit la taille de int
(nécessitant éventuellement l'opération de droite d'une soustraction ou unaire moins pour être promu au prochain type signé plus grand si int
est de 32 bits ou moins), tandis qu'un wrap32_t
se comporterait toujours comme un membre d'un anneau algébrique (blocage des promotions même si int
était supérieur à 32 bits). En l'absence de tels types, cependant, il est souvent impossible d'écrire du code qui est à la fois portable et propre, car le code portable nécessitera souvent des contraintes de type partout.
Les règles de conversion numérique en C et C++ sont un gâchis byzantin. L'utilisation de types non signés vous expose beaucoup plus à ce gâchis que l'utilisation de types purement signés.
Prenons par exemple le cas simple d'une comparaison entre deux variables, l'une signée et l'autre non signée.
Pour prendre un autre exemple, envisagez de multiplier deux entiers non signés de la même taille.