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 passant à un autre, 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.
la source
int c = INT.MAXINT; c+=1;
et voir ce qui est arrivé à c.Réponses:
Non. En C, les variables ont un ensemble fixe d'adresses mémoire avec lesquelles travailler. Si vous travaillez sur un système à 4 octets
ints
, et que vous définissez uneint
variable sur2,147,483,647
puis ajoutez1
, 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.
Considéré de manière binaire, si le type ne peut stocker que 8 bits et que vous essayez de forcer la valeur
1010101010101
avec un cas, vous vous retrouverez avec les 8 derniers bits, ou01010101
.Dans votre exemple, quelle que soit votre action
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 se traduira par-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
.la source
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 il n'y a aucune garantie 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/951890
la source
int
. je suppose qu'ils pourraient utiliser le code Gray ou BCD ou EBCDIC . Je ne sais pas pourquoi quelqu'un concevrait du matériel pour faire de l'arithmétique avec du code Gray ou EBCDIC, mais là encore, je ne sais pas pourquoi quelqu'un ferait duunsigned
binaire et signeraitint
avec autre chose que le complément de 2.Donc, il y a deux choses ici:
Au niveau linguistique:
En C:
Pour ceux qui voudraient un exemple "quoi que ce soit", j'ai vu:
changer en:
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 Sanitaire de comportement non défini qui abandonnera en cas de dépassement / dépassement d'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 l' assainisseur d'adresses 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.
la source
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 ensuite effectuée sur les données dans 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.
la source
Tout d'abord (en supposant la norme C99), vous souhaiterez peut-être inclure
<stdint.h>
un en-tête standard et utiliser certains des types définis ici, notammentint32_t
qui est exactement un entier signé 32 bits, ouuint64_t
qui est exactement un entier non signé 64 bits, et ainsi de suite. Vous souhaiterez peut-être utiliser des types commeint_fast16_t
pour des raisons de performances.Lisez les autres réponses 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. Méfiez-vous des comportements non définis lors d'un débordement signé .
Ensuite, si vous devez calculer des nombres entiers exactement énormes (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 un mot 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
la source