Pourquoi les langues ne génèrent-elles pas d’erreurs sur le dépassement d’entier par défaut?

Dans plusieurs langages de programmation modernes (y compris C ++, Java et C #), le langage autorise le débordement d’entier à l’exécution sans soulever aucune condition d’erreur.

Par exemple, considérons cette méthode (artificielle) C #, qui ne tient pas compte de la possibilité de débordement / débordement. (Par souci de concision, la méthode ne gère pas non plus le cas où la liste spécifiée est une référence nulle.)

//Returns the sum of the values in the specified list. private static int sumList(List list) { int sum = 0; foreach (int listItem in list) { sum += listItem; } return sum; } 

Si cette méthode est appelée comme suit:

 List list = new List(); list.Add(2000000000); list.Add(2000000000); int sum = sumList(list); 

Un débordement se produira dans la méthode sumList() (car le type int en C # est un entier signé 32 bits et la sum des valeurs de la liste dépasse la valeur de l’entier signé 32 bits maximum). La variable sum aura une valeur de -294967296 (pas une valeur de 4000000000); Ce n’est probablement pas ce que le développeur (hypothétique) de la méthode sumList a voulu.

De toute évidence, les développeurs peuvent utiliser différentes techniques pour éviter la possibilité de débordement d’entier, comme l’utilisation d’un type comme le BigInteger de Java, ou le mot clé checked et le commutateur de compilation /checked dans C #.

Cependant, la question qui m’intéresse est la suivante: pourquoi ces langages ont-ils été conçus pour autoriser par défaut les dépassements d’entiers au lieu, par exemple, de générer une exception lorsqu’une opération est exécutée à l’exécution? débordement. Il semblerait qu’un tel comportement permettrait d’éviter les bogues dans les cas où un développeur néglige de prendre en compte la possibilité de débordement lors de l’écriture de code effectuant une opération arithmétique pouvant entraîner un débordement. (Ces langages auraient pu inclure quelque chose comme un mot clé “non contrôlé” qui pourrait désigner un bloc où le dépassement d’entier est autorisé sans qu’une exception soit déclenchée, dans les cas où ce comportement est explicitement prévu par le développeur; )

La réponse se résume-t-elle à la performance – les concepteurs de langage ne voulaient pas que leurs langages respectifs aient des opérations arithmétiques entières “lentes”, le temps d’exécution nécessitant un travail supplémentaire pour vérifier si un débordement s’est produit opération – et cette considération de performance a surpassé la valeur d’éviter les défaillances “silencieuses” dans le cas où un débordement accidentel se produit?

Existe-t-il d’autres raisons à cette décision de conception linguistique, autres que des considérations de performance?

En C #, c’était une question de performance. Plus précisément, l’parsing comparative prête à l’emploi.

Lorsque C # était nouveau, Microsoft espérait que beaucoup de développeurs C ++ y passeraient. Ils savaient que de nombreuses personnes C ++ pensaient que C ++ était rapide, en particulier plus rapide que les langages qui gaspillaient du temps sur la gestion automatique de la mémoire, etc.

Les adopteurs potentiels et les critiques de magazines sont susceptibles d’obtenir une copie du nouveau code C #, de l’installer, de créer une application sortingviale que personne n’écrirait jamais dans le monde réel, de l’exécuter en boucle et de mesurer le temps nécessaire. Ensuite, ils prendraient une décision pour leur entreprise ou publieraient un article basé sur ce résultat.

Le fait que leur test ait montré que C # était plus lent que C ++ compilé en mode natif était le genre de chose qui détournait rapidement les utilisateurs de C #. Le fait que votre application C # va attraper automatiquement un dépassement de capacité / débordement est le genre de chose qui pourrait leur manquer. Donc, c’est désactivé par défaut.

Je pense qu’il est évident que 99% du temps nous voulons / vérifions pour être sur. C’est un compromis regrettable.

Je pense que la performance est une bonne raison. Si vous considérez chaque instruction dans un programme typique qui incrémente un nombre entier, et si au lieu de l’op simple d’append 1, il fallait vérifier chaque fois si l’ajout de 1 déborderait le type, alors le coût en cycles supplémentaires serait assez sévère.

Vous travaillez en supposant que le débordement d’entier est toujours un comportement indésirable.

Parfois, le dépassement d’entier est le comportement souhaité. Un exemple que j’ai vu est la représentation d’une valeur de titre absolue en tant que nombre à virgule fixe. Étant donné que unsigned int, 0 est 0 ou 360 degrés et l’entier non signé 32 bits max (0xffffffff) est la plus grande valeur juste en dessous de 360 ​​degrés.

 int main() { uint32_t shipsHeadingInDegrees= 0; // Rotate by a bunch of degrees shipsHeadingInDegrees += 0x80000000; // 180 degrees shipsHeadingInDegrees += 0x80000000; // another 180 degrees, overflows shipsHeadingInDegrees += 0x80000000; // another 180 degrees // Ships heading now will be 180 degrees cout << "Ships Heading Is" << (double(shipsHeadingInDegrees) / double(0xffffffff)) * 360.0 << std::endl; } 

Il y a probablement d'autres situations où le dépassement est acceptable, similaire à cet exemple.

C’est probablement 99% de performance. Sur x86, il faudrait vérifier l’indicateur de débordement à chaque opération, ce qui serait un énorme problème de performance.

Les 1% restants couvriraient les cas où des personnes effectuent des manipulations fantaisistes ou sont imprécises dans le mélange d’opérations signées et non signées et veulent la sémantique de débordement.

Parce que la recherche de débordement prend du temps. Chaque opération mathématique primitive, qui se traduit normalement par une instruction d’assemblage unique, devrait inclure une vérification du débordement, entraînant de multiples instructions d’assemblage, ce qui pourrait entraîner un programme plusieurs fois plus lent.

C / C ++ n’impose jamais le comportement du piège. Même la division évidente par 0 est un comportement indéfini en C ++, pas un type de piège spécifié.

Le langage C n’a aucun concept de piégeage, sauf si vous comptez des signaux.

C ++ a un principe de conception selon lequel il n’introduit pas de surcharge non présente dans C, sauf si vous le demandez. Stroustrup n’aurait donc pas voulu obliger les entiers à se comporter d’une manière qui nécessite une vérification explicite.

Certains compilateurs antérieurs et des implémentations légères pour du matériel restreint ne prennent pas du tout en charge les exceptions, et les exceptions peuvent souvent être désactivées avec les options du compilateur. Les exceptions obligatoires pour les langues intégrées seraient problématiques.

Même si C ++ avait vérifié les entiers, 99% des programmeurs dans les premiers jours se seraient tournés vers le bas pour améliorer les performances …

La compatibilité ascendante est importante. Avec C, il était supposé que vous accordiez suffisamment d’attention à la taille de vos types de données. Ensuite, avec C ++, C # et Java, très peu de choses ont changé avec le fonctionnement des types de données “intégrés”.

Ma compréhension des raisons pour lesquelles les erreurs ne seraient pas générées par défaut à l’exécution se résume à la nécessité de créer des langages de programmation avec un comportement de type ACID. Plus précisément, le principe selon lequel tout ce que vous codez pour faire (ou ne pas coder), il fera (ou ne fera pas). Si vous n’avez pas codé un gestionnaire d’erreurs, la machine “assumera”, en raison de l’absence de gestionnaire d’erreurs, que vous voulez vraiment faire la chose ridicule et sujette à un accident que vous lui dites.

(Référence ACID: http://en.wikipedia.org/wiki/ACID )