55 votes

C ++ plante dans une boucle 'pour' avec une expression négative

Le code suivant bloque C ++ avec une erreur d'exécution:

 #include <string>

using namespace std;

int main() {
    string s = "aa";
    for (int i = 0; i < s.length() - 3; i++) {

    }
}
 

Alors que ce code ne plante pas:

 #include <string>

using namespace std;

int main() {
    string s = "aa";
    int len = s.length() - 3;
    for (int i = 0; i < len; i++) {

    }
}
 

Je n'ai juste aucune idée de comment l'expliquer. Quelle pourrait être la raison de ce comportement?

85voto

Antonio Points 2246

s.length() est de type entier non signé. Lorsque vous soustrayez 3, vous rendre négatif. Pour un unsigned, cela signifie très grand.

Une solution de contournement (valable aussi longtemps que la chaîne est longue jusqu'à INT_MAX) serait de faire comme ceci:

#include <string>

using namespace std;

int main() {

    string s = "aa";

    for (int i = 0; i < static_cast<int> (s.length() ) - 3; i++) {

    }
}

Qui ne serait jamais entrer dans la boucle.

Un détail très important, c'est que vous avez probablement reçu un avertissement de "comparer des entiers signés et non signés de la valeur". Le problème est que si vous l'ignorer ces avertissements, vous entrez dans la très dangereuse domaine de l' implicite "conversion d'entier"(*), qui a un comportement défini, mais il est difficile à suivre: le mieux est de ne jamais ignorer ces avertissements du compilateur.


(*) Vous pourriez également être intéressé de savoir à propos de "integer promotion".

28voto

stefan Points 4987

Tout d'abord: pourquoi ça plante? Nous allons étape par le biais de votre programme comme un débogueur serait.

Note: je vais supposer que votre corps de boucle n'est pas vide, mais accède à la chaîne. Si ce n'est pas le cas, la cause de l'accident est un comportement indéterminé par débordement d'entier. Voir Richard Hansen réponse pour cela.

std::string s = "aa";//assign the two-character string "aa" to variable s of type std::string
for ( int i = 0; // create a variable i of type int with initial value 0 
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 1!
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 2!
i < s.length() - 3 // call s.length(), subtract 3, compare the result with i. OK!
{...} // execute loop body
i++ // do the incrementing part of the loop, i now holds value 3!
.
.

Nous attendons de la case i < s.length() - 3 à l'échec tout de suite, puisque la longueur d' s est de deux (nous n'tous donné une longueur au début et n'a jamais changé) et 2 - 3 est -1, 0 < -1 est faux. Cependant, nous ne pouvons obtenir un "OK" ici.

C'est parce qu' s.length() n'est pas 2. Il est 2u. std::string::length() a type de retour size_t qui est un entier non signé. Pour en revenir à la condition de la boucle, nous avons d'abord obtenir la valeur de s.length(), alors 2u, maintenant soustraire 3. 3 est un entier littéral et interprété par le compilateur comme type int. Ainsi, le compilateur doit calculer 2u - 3, deux des valeurs de types différents. Les opérations sur les types primitifs ne fonctionnent pour les mêmes types, donc on doit être converti dans l'autre. Il existe des règles strictes, dans ce cas, unsigned "gagne", alors 3 est converti 3u. Dans des entiers non signés, 2u - 3u ne peut pas être -1u que ce nombre n'existe pas (bien, parce que c'est un signe bien sûr!). Au lieu de cela, il calcule chaque opération modulo 2^(n_bits)n_bits est le nombre de bits dans ce type (généralement de 8, 16, 32 ou 64). Ainsi, au lieu de -1 nous obtenons 4294967295u (en supposant 32 bits).

Alors maintenant, le compilateur est fait avec s.length() - 3 (bien sûr, il est beaucoup beaucoup plus rapide que moi ;-) ), passons maintenant à la comparaison: i < s.length() - 3. Mettre dans les valeurs: 0 < 4294967295u. Encore une fois, les différents types, 0 devient 0u, la comparaison 0u < 4294967295u est évidemment vrai, la condition de la boucle est positivement vérifiée, nous pouvons maintenant exécuter le corps de la boucle.

Après l'incrémentation, la seule chose qui change dans le ci-dessus est la valeur de i. La valeur de i sera de nouveau converti en un unsigned int, comme la comparaison des besoins.

Nous avons donc

(0u < 4294967295u) == true, let's do the loop body!
(1u < 4294967295u) == true, let's do the loop body!
(2u < 4294967295u) == true, let's do the loop body!

Voici le problème: Que faites-vous dans le corps de la boucle? Sans doute vous avez accès à l' i^th caractère de votre chaîne, n'est-ce pas? Même si ce n'était pas votre intention, vous n'avez pas seulement consulté le zéro et d'abord, mais aussi le second! Le second n'existe pas (en tant que votre chaîne ne dispose que de deux caractères, le zéro et le premier), vous avez accès à la mémoire, vous ne devriez pas, le programme fait ce qu'il veut (comportement indéfini). À noter que le programme n'est pas nécessaire de crash immédiatement. Il semble bien fonctionner pour une autre demi-heure, de sorte que ces erreurs sont difficiles à attraper. Mais il est toujours dangereux d'accéder à la mémoire au-delà des limites, c'est là que la plupart des accidents viennent.

Donc en résumé, vous obtenez une valeur différente de s.length() - 3 que ce que vous attendez, il en résulte un positif de la boucle de contrôle de condition, qui conduit à répétitif de l'exécution du corps de la boucle, ce qui en soi accède à la mémoire, il ne devrait pas.

Maintenant, nous allons voir comment éviter que, soit la façon de dire au compilateur que vous avez fait dans votre condition de boucle.


Longueurs de chaînes et les tailles de conteneurs sont intrinsèquement non signé , donc vous devez utiliser un entier non signé dans les boucles for.

Depuis unsigned int est assez long et donc pas souhaitable d'écrire encore et encore, en boucle, il suffit d'utiliser size_t. C'est le type de tous les conteneurs de la STL utilise pour stocker la longueur ou la taille. Vous devrez peut-être inclure cstddef à affirmer l'indépendance de plate-forme.

#include <cstddef>
#include <string>

using namespace std;

int main() {

    string s = "aa";

    for ( size_t i = 0; i + 3 < s.length(); i++) {
    //    ^^^^^^         ^^^^
    }
}

Depuis a < b - 3 est mathématiquement équivalente à a + 3 < b, nous pouvons les échange. Toutefois, a + 3 < b empêche b - 3 à être d'une grande valeur. Rappelons qu' s.length() renvoie un entier non signé et non signé entiers effectuer des opérations de module 2^(bits) où les bits est le nombre de bits dans le type (généralement de 8, 16, 32 ou 64). Donc avec s.length() == 2, s.length() - 3 == -1 == 2^(bits) - 1.


Alternativement, si vous souhaitez utiliser i < s.length() - 3 pour la préférence personnelle, vous devez ajouter une condition:

for ( size_t i = 0; (s.length() > 3) && (i < s.length() - 3); ++i )
//    ^             ^                    ^- your actual condition
//    ^             ^- check if the string is long enough
//    ^- still prefer unsigned types!

12voto

Joachim Pileborg Points 121221

En fait, dans la première version que vous avez en boucle pendant un temps très long, comme vous le comparer i pour un unsigned integer contenant un très grand nombre. De la taille d'une chaîne de caractères est (en effet) le même que size_t qui est un entier non signé. Lorsque vous soustrayez l' 3 de cette valeur, il underflows et continue d'être d'une grande valeur.

Dans la deuxième version du code, vous devez l'assigner cette valeur non signé signé d'une variable, et de façon à obtenir la valeur correcte.

Et ce n'est pas vraiment l'état ou à la valeur que les causes de l'accident, il est plus probable que vous l'indice de la chaîne en dehors des limites, cas de comportement non défini.

5voto

Richard Hansen Points 13044

En supposant que vous avez laissé de côté le code important dans l' for boucle

La plupart des gens ici semblent incapables de reproduire le crash—moi y compris—et il semble que les autres réponses données ici sont basées sur l'hypothèse que vous avez laissé de côté certaines de code dans le corps de l' for boucle, et que le code manquant est ce qui est à l'origine de votre panne.

Si vous utilisez i d'accéder à la mémoire (sans doute de caractères dans la chaîne) dans le corps de l' for boucle, et vous avez laissé ce code de votre question dans une tentative de fournir un exemple minimal, puis le crash s'explique aisément par le fait qu' s.length() - 3 a la valeur SIZE_MAX en raison de l'arithmétique modulaire sur les entiers non signés types. SIZE_MAX est un très grand nombre, i vont continuer à grandir jusqu'à ce qu'il est utilisé pour accéder à une adresse qui déclenche une erreur de segmentation.

Cependant, votre code pourrait théoriquement un accident en tant que-est, même si le corps de l' for boucle est vide. Je ne suis pas au courant de toutes les implémentations qui se bloque, mais peut-être que votre compilateur et le PROCESSEUR sont exotiques.

Les explications suivantes ne suppose pas que vous avez quitté le code dans votre question. Il prend sur la foi que le code que vous avez posté votre question se bloque, comme-est; qu'il n'est pas une présentation abrégée de stand-in pour un autre code qui se bloque.

Pourquoi votre premier programme se bloque

Votre premier programme se bloque parce que c'est sa réaction à un comportement indéfini dans votre code. (Quand j'essaie de l'exécution de votre code, il met fin sans s'écraser parce que c'est ma mise en œuvre de la réaction au comportement non défini.)

Le comportement non défini vient de déborder un int. Le C++11 standard dit (dans [expr] la clause 5, paragraphe 4):

Si, lors de l'évaluation d'une expression, le résultat n'est pas définie mathématiquement ou pas dans la gamme des représentable valeurs pour son type, le comportement est indéfini.

Dans votre exemple de programme, s.length() renvoie un size_t de la valeur 2. En soustrayant 3 de ce que serait le rendement négatif de 1, sauf size_t est un type entier non signé. Le C++11 standard dit (dans [de base.fondamentaux] la clause 3.9.1 paragraphe 4):

Des entiers non signés, a déclaré unsigned, doit obéir aux lois de l'arithmétique modulo 2nn est le nombre de bits dans la représentation de la valeur de la taille de l'entier.46

46) Cela implique que non signé de l'arithmétique ne déborde pas, car un résultat qui ne peut pas être représenté par la résultante de type entier non signé est réduite modulo le nombre qui est plus grand que la plus grande valeur qui peut être représentée par la résultante de type entier non signé.

Cela signifie que le résultat de l' s.length() - 3 est size_t valeur SIZE_MAX. C'est un nombre très grand, plus grand que INT_MAX (la plus grande valeur représentable par int).

Parce qu' s.length() - 3 est si grand, de l'exécution tourne en boucle jusqu'à l' i obtient à l' INT_MAX. Sur la très prochaine itération, lorsqu'il cherche à incrémenter i, INT_MAX + 1, mais qui n'est pas dans la gamme de représentable, les valeurs de int. Ainsi, le comportement est indéfini. Dans votre cas, le comportement est en panne.

Sur mon système, ma mise en œuvre du comportement lors de l' i est incrémenté passé, INT_MAX est de retour à la ligne (ensemble i de INT_MIN) et de continuer. Une fois i atteint -1, l'habitude de l'arithmétique des conversions (C++ [expr] article 5, paragraphe 9) cause i de l'égalité des SIZE_MAX de sorte que la boucle se termine.

Soit la réaction est appropriée. C'est le problème avec un comportement indéfini—il peut fonctionner comme vous le souhaitez, il peut tomber en panne, il peut formater votre disque dur, ou il peut annuler Firefly. Vous ne savez jamais.

Comment votre deuxième programme permet d'éviter le crash

Comme avec le premier programme, s.length() - 3 est size_t type avec la valeur SIZE_MAX. Cependant, cette fois la valeur est assignée à un int. Le C++11 standard dit (dans [conv.intégrale] la clause 4.7 paragraphe 3):

Si le type de destination est signé, la valeur est inchangée si elle peut être représentée dans le type de destination (et bit-largeur de champ); sinon, la valeur est mise en œuvre définies.

La valeur SIZE_MAX est trop grande pour être représentable par un int, alors len obtient une mise en valeur définie (probablement -1, mais peut-être pas). La condition i < len finira par être vrai quelle que soit la valeur attribuée à l' len, de sorte que votre programme se terminera sans rencontrer un comportement indéfini.

3voto

FatihK Points 4776

Le type de s.longueur() size_t avec une valeur de 2, donc s.length() - 3 est également un type non signé size_t et il a une valeur de SIZE_MAX qui est mise en œuvre (ce qui est 18446744073709551615 si sa taille est de 64 bits). Il est au moins 32 bits de type (peut-être 64 bits et 64 bits des plates-formes) et ce nombre élevé signifie une boucle indéterminée. Pour éviter ce problème, vous pouvez tout simplement jeté s.length() de int:

for (int i = 0; i < (int)s.length() - 3; i++)
{
          //..some code causing crash
}

Dans le second cas len -1 parce que c'est un signed integer et n'entre pas dans la boucle.

Quand il s'agit de s'écraser, ce "infini" de la boucle n'est pas la cause directe de l'accident. Si vous partagez le code dans la boucle, vous pouvez obtenir de plus amples explications.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X