J'ai passé en revue la programmation C et il y a juste quelques choses qui me dérangent.
Prenons ce code par exemple:
int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);
Je sais qu'un int peut contenir une valeur maximale positive de 2.147.483.647. Donc, en allant sur ce point, cela "déborde" à l'adresse de mémoire suivante, ce qui fait que l'élément 2 apparaît comme "-2147483648" à cette adresse? Mais cela n'a pas vraiment de sens car dans la sortie, il est toujours dit que la prochaine adresse contient la valeur 4, puis 5. Si le nombre avait débordé à l'adresse suivante, cela ne changerait-il pas la valeur stockée à cette adresse ?
Je me souviens vaguement de la programmation dans MIPS Assembly et de regarder les adresses changer les valeurs pendant le programme étape par étape que les valeurs affectées à ces adresses changeraient.
Sauf si je me souviens mal, voici une autre question: si le numéro attribué à une adresse spécifique est plus grand que le type (comme dans mon tableau [2]), cela n'affecte-t-il pas les valeurs stockées à l'adresse suivante?
Exemple: Nous avons int myNum = 4 milliards à l'adresse 0x10010000. Bien sûr, myNum ne peut pas stocker 4 milliards, il apparaît donc comme un nombre négatif à cette adresse. Bien qu'il ne soit pas en mesure de stocker ce grand nombre, cela n'a aucun effet sur la valeur stockée à l'adresse suivante de 0x10010004. Correct?
Les adresses mémoire ont juste assez d'espace pour contenir certaines tailles de nombres/caractères, et si la taille dépasse la limite alors elle sera représentée différemment (comme essayer de stocker 4 milliards dans l'int, mais elle apparaîtra comme un nombre négatif) et cela n'a donc aucun effet sur les nombres/caractères stockés à l'adresse suivante.
Désolé si je suis allé trop loin. J'ai eu un gros problème cérébral toute la journée.
Non. En C, les variables ont un ensemble fixe d'adresses mémoire avec lesquelles travailler. Si vous travaillez sur un système avec 4 octets ints
et que vous définissez une variable int
sur 2,147,483,647
puis ajoutez 1
, la variable contiendra généralement -2147483648
. (Sur la plupart des systèmes. Le comportement n'est en fait pas défini.) Aucun autre emplacement de mémoire ne sera modifié.
En substance, le compilateur ne vous permettra pas d'attribuer une valeur trop grande pour le type. Cela générera une erreur de compilation. Si vous le forcez avec un cas, la valeur sera tronquée.
Vu sous forme de bits, si le type ne peut stocker que 8 bits et que vous essayez de forcer la valeur 1010101010101
avec un boîtier, vous vous retrouverez avec les 8 derniers bits, ou 01010101
.
Dans votre exemple, indépendamment de ce que vous faites pour myArray[2]
, myArray[3]
contiendra '4'. Il n'y a pas de "débordement". Vous essayez de mettre quelque chose de plus de 4 octets, il se contentera de tout couper en haut de gamme, laissant les 4 derniers octets. Sur la plupart des systèmes, cela entraînera -2147483648
.
D'un point de vue pratique, vous voulez simplement vous assurer que cela n'arrive jamais. Ces sortes de débordements entraînent souvent des défauts difficiles à résoudre. En d'autres termes, si vous pensez qu'il y a une chance que vos valeurs se chiffrent en milliards, n'utilisez pas int
.
Le dépassement d'entier signé est un comportement non défini. Si cela se produit, votre programme n'est pas valide. Le compilateur n'est pas tenu de vérifier cela pour vous, il peut donc générer un exécutable qui semble faire quelque chose de raisonnable, mais rien ne garantit qu'il le fera.
Cependant, le dépassement d'entier non signé est bien défini. Il encapsulera le modulo UINT_MAX + 1. La mémoire non occupée par votre variable ne sera pas affectée.
Voir aussi https://stackoverflow.com/q/18195715/95189
Donc, il y a deux choses ici:
Au niveau de la langue:
En C:
Pour ceux qui voudraient un exemple "quoi que ce soit", j'ai vu:
for (int i = 0; i >= 0; i++) {
...
}
changer en:
for (int i = 0; true; i++) {
...
}
et oui, c'est une transformation légitime.
Cela signifie qu'il existe en effet des risques potentiels d'écrasement de la mémoire en cas de débordement en raison d'une transformation étrange du compilateur.
Remarque: sur Clang ou gcc, utilisez -fsanitize=undefined
dans Debug pour activer le ndefined Behavior Sanitizer qui abandonnera en cas de dépassement/dépassement des entiers signés.
Ou cela signifie que vous pouvez remplacer la mémoire en utilisant le résultat de l'opération d'indexation (non cochée) dans un tableau. Ceci est malheureusement beaucoup plus probable en l'absence de détection de débordement/débordement.
Remarque: sur Clang ou gcc, utilisez -fsanitize=address
dans Debug pour activer Address Sanitizer qui abandonnera l'accès hors limites.
Au niveau de la machine :
Cela dépend vraiment des instructions d'assemblage et du processeur que vous utilisez:
Add
: Notez que si des choses se produisent dans les registres ou la mémoire, dans aucun cas le CPU n'écrase la mémoire en cas de débordement.
Pour approfondir la réponse de @ StevenBurnap, la raison pour laquelle cela se produit est à cause du fonctionnement des ordinateurs au niveau de la machine.
Votre baie est stockée en mémoire (par exemple en RAM). Lorsqu'une opération arithmétique est effectuée, la valeur en mémoire est copiée dans les registres d'entrée du circuit qui effectue l'arithmétique (l'ALU: Arithmetic Logic Unit ), l'opération est alors effectuée sur les données de les registres d'entrée, produisant un résultat dans le registre de sortie. Ce résultat est ensuite recopié en mémoire à la bonne adresse en mémoire, laissant les autres zones de mémoire intactes.
Tout d'abord (en supposant la norme C99), vous pouvez inclure <stdint.h>
en-tête standard et utilisez certains des types définis ici, notamment int32_t
qui est exactement un entier signé de 32 bits, ou uint64_t
qui est exactement un entier non signé de 64 bits, et ainsi de suite. Vous souhaiterez peut-être utiliser des types tels que int_fast16_t
pour des raisons de performances.
Lisez les réponses des autres expliquant que l'arithmétique non signée ne se déverse pas (ou ne déborde pas) vers des emplacements de mémoire adjacents. Attention au comportement indéfini sur débordement signé .
Ensuite, si vous devez calculer exactement d'énormes nombres entiers (par exemple, vous voulez calculer la factorielle de 1000 avec tous ses 2568 chiffres en décimal), vous voulez des bigints aka nombres de précision arbitraires (ou bignums). Les algorithmes pour une arithmétique bigint efficace sont très intelligents et nécessitent généralement l'utilisation d'instructions machine spécialisées (par exemple, certains ajoutent Word avec carry, si votre processeur en dispose). Par conséquent, je recommande fortement dans ce cas d'utiliser une bibliothèque bigint existante comme GMPlib