J'attribue des valeurs dans un programme C ++ hors des limites comme ceci:
#include <iostream>
using namespace std;
int main()
{
int array[2];
array[0] = 1;
array[1] = 2;
array[3] = 3;
array[4] = 4;
cout << array[3] << endl;
cout << array[4] << endl;
return 0;
}
Le programme imprime 3
et 4
. Cela ne devrait pas être possible. J'utilise g ++ 4.3.3
Voici la commande de compilation et d'exécution
$ g++ -W -Wall errorRange.cpp -o errorRange
$ ./errorRange
3
4
Ce n'est que lors de l'attribution array[3000]=3000
que cela me donne un défaut de segmentation.
Si gcc ne vérifie pas les limites du tableau, comment puis-je être sûr que mon programme est correct, car cela peut entraîner de graves problèmes plus tard?
J'ai remplacé le code ci-dessus par
vector<int> vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;
et celui-ci ne produit également aucune erreur.
vector
ne redimensionne pas automatiquement lors de l'accès à des éléments hors limites! C'est juste UB!Réponses:
Bienvenue au meilleur ami de tous les programmeurs C / C ++: Undefined Behavior .
Il y a beaucoup de choses qui ne sont pas spécifiées par la norme de langue, pour diverses raisons. C'est l'un d'eux.
En général, chaque fois que vous rencontrez un comportement non défini, tout peut arriver. L'application peut planter, elle peut se figer, elle peut éjecter votre lecteur de CD-ROM ou faire sortir des démons de votre nez. Il peut formater votre disque dur ou envoyer par e-mail tout votre porno à votre grand-mère.
Cela peut même, si vous êtes vraiment malchanceux, sembler fonctionner correctement.
Le langage dit simplement ce qui devrait se passer si vous accédez aux éléments dans les limites d'un tableau. Ce qui se passe si vous sortez des limites n'est pas défini. Cela peut sembler fonctionner aujourd'hui, sur votre compilateur, mais ce n'est pas du C ou du C ++ légal, et il n'y a aucune garantie que cela fonctionnera toujours la prochaine fois que vous exécuterez le programme. Ou qu'il n'a pas écrasé les données essentielles même maintenant, et que vous avez tout simplement pas rencontré les problèmes, qu'il est va causer - encore.
Quant à savoir pourquoi il n'y a pas de vérification des limites, il y a plusieurs aspects à la réponse:
std::vector
modèle de classe, qui permet les deux.operator[]
est conçu pour être efficace. Le standard de langage n'exige pas qu'il effectue une vérification des limites (bien qu'il ne l'interdise pas non plus). Un vecteur a également laat()
fonction membre qui garantit la vérification des limites. Ainsi, en C ++, vous obtenez le meilleur des deux mondes si vous utilisez un vecteur. Vous obtenez des performances de type tableau sans vérification des limites, et vous avez la possibilité d'utiliser un accès vérifié par les limites quand vous le souhaitez.la source
Avec g ++, vous pouvez ajouter l'option de ligne de commande:
-fstack-protector-all
.Sur votre exemple, cela a abouti à ce qui suit:
> g++ -o t -fstack-protector-all t.cc > ./t 3 4 /bin/bash: line 1: 15450 Segmentation fault ./t
Cela ne vous aide pas vraiment à trouver ou à résoudre le problème, mais au moins le segfault vous fera savoir que quelque chose ne va pas.
la source
-fsanitize=address
ce qui intercepte ce bogue à la fois au moment de la compilation (si optimisation) et à l'exécution.-fsanitize=undefined,address
. Mais il convient de noter qu'il existe de rares cas de coin avec une bibliothèque std, lorsque l'accès hors limites n'est pas détecté par le désinfectant . Pour cette raison, je recommande d'utiliser en plus l'-D_GLIBCXX_DEBUG
option, qui ajoute encore plus de vérifications.g ++ ne vérifie pas les limites du tableau, et vous pouvez écraser quelque chose avec 3,4 mais rien de vraiment important, si vous essayez avec des nombres plus élevés, vous aurez un crash.
Vous écrasez simplement des parties de la pile qui ne sont pas utilisées, vous pouvez continuer jusqu'à ce que vous atteigniez la fin de l'espace alloué pour la pile et elle finirait par planter
EDIT: Vous n'avez aucun moyen de gérer cela, peut-être qu'un analyseur de code statique pourrait révéler ces échecs, mais c'est trop simple, vous pouvez avoir des échecs similaires (mais plus complexes) non détectés même pour les analyseurs statiques
la source
C'est un comportement indéfini pour autant que je sache. Exécutez un programme plus volumineux avec cela et il plantera quelque part en cours de route. La vérification des limites ne fait pas partie des tableaux bruts (ni même de std :: vector).
Utilisez
std::vector::iterator
plutôt std :: vector avec 's pour ne pas avoir à vous en soucier.Éditer:
Juste pour le plaisir, exécutez ceci et voyez combien de temps avant que vous ne plantiez:
int main() { int array[1]; for (int i = 0; i != 100000; i++) { array[i] = i; } return 0; //will be lucky to ever reach this }
Edit2:
Ne lancez pas ça.
Edit3:
OK, voici une leçon rapide sur les tableaux et leurs relations avec les pointeurs:
Lorsque vous utilisez l'indexation de tableaux, vous utilisez en réalité un pointeur déguisé (appelé «référence»), qui est automatiquement déréférencé. C'est pourquoi au lieu de * (array [1]), array [1] renvoie automatiquement la valeur à cette valeur.
Lorsque vous avez un pointeur vers un tableau, comme ceci:
int array[5]; int *ptr = array;
Ensuite, le "tableau" dans la deuxième déclaration se désintègre vraiment en un pointeur vers le premier tableau. C'est un comportement équivalent à ceci:
int *ptr = &array[0];
Lorsque vous essayez d'accéder au-delà de ce que vous avez alloué, vous utilisez simplement un pointeur vers une autre mémoire (dont C ++ ne se plaindra pas). En prenant mon exemple de programme ci-dessus, cela équivaut à ceci:
int main() { int array[1]; int *ptr = array; for (int i = 0; i != 100000; i++, ptr++) { *ptr++ = i; } return 0; //will be lucky to ever reach this }
Le compilateur ne se plaindra pas car en programmation, vous devez souvent communiquer avec d'autres programmes, en particulier le système d'exploitation. Cela se fait un peu avec des pointeurs.
la source
Allusion
Si vous voulez avoir des tableaux de taille rapide avec contrainte plage contrôle d'erreur, essayez d' utiliser boost :: tableau , (également std :: tr1 :: tableau de
<tr1/array>
ce sera conteneur standard dans le prochain C ++ spécification). C'est beaucoup plus rapide que std :: vector. Il réserve de la mémoire sur le tas ou à l'intérieur de l'instance de classe, tout comme int array [].Voici un exemple de code simple:
#include <iostream> #include <boost/array.hpp> int main() { boost::array<int,2> array; array.at(0) = 1; // checking index is inside range array[1] = 2; // no error check, as fast as int array[2]; try { // index is inside range std::cout << "array.at(0) = " << array.at(0) << std::endl; // index is outside range, throwing exception std::cout << "array.at(2) = " << array.at(2) << std::endl; // never comes here std::cout << "array.at(1) = " << array.at(1) << std::endl; } catch(const std::out_of_range& r) { std::cout << "Something goes wrong: " << r.what() << std::endl; } return 0; }
Ce programme imprimera:
array.at(0) = 1 Something goes wrong: array<>: index out of range
la source
C ou C ++ ne vérifiera pas les limites d'un accès à un tableau.
Vous allouez le tableau sur la pile. L'indexation du tableau via
array[3]
équivaut à *(array + 3)
, où array est un pointeur vers & array [0]. Cela entraînera un comportement indéfini.Une façon d'attraper cela parfois en C est d'utiliser un vérificateur statique, tel que splint . Si vous exécutez:
splint +bounds array.c
sur,
int main(void) { int array[1]; array[1] = 1; return 0; }
alors vous obtiendrez l'avertissement:
la source
Vous écrasez certainement votre pile, mais le programme est suffisamment simple pour que les effets de cela passent inaperçus.
la source
Exécutez ceci via Valgrind et vous pourriez voir une erreur.
Comme l'a souligné Falaina, valgrind ne détecte pas de nombreuses instances de corruption de pile. Je viens d'essayer l'échantillon sous valgrind, et il ne signale en effet aucune erreur. Cependant, Valgrind peut jouer un rôle déterminant dans la recherche de nombreux autres types de problèmes de mémoire, ce n'est tout simplement pas particulièrement utile dans ce cas, sauf si vous modifiez votre bulid pour inclure l'option --stack-check. Si vous générez et exécutez l'exemple en tant que
g++ --stack-check -W -Wall errorRange.cpp -o errorRange valgrind ./errorRange
valgrind fera une erreur.
la source
Un comportement indéfini joue en votre faveur. Le souvenir que vous écrasez n'a apparemment rien d'important. Notez que C et C ++ ne vérifient pas les limites sur les tableaux, donc des trucs comme ça ne seront pas pris au moment de la compilation ou de l'exécution.
la source
Lorsque vous initialisez le tableau avec
int array[2]
, un espace pour 2 entiers est alloué; mais l'identifiantarray
pointe simplement vers le début de cet espace. Lorsque vous accédez ensuite àarray[3]
etarray[4]
, le compilateur incrémente simplement cette adresse pour indiquer où ces valeurs seraient, si le tableau était suffisamment long; essayez d'accéder à quelque chose commearray[42]
sans l'initialiser au préalable, vous finirez par obtenir la valeur qui se trouvait déjà en mémoire à cet emplacement.Éditer:
Plus d'informations sur les pointeurs / tableaux: http://home.netcom.com/~tjensen/ptr/pointers.htm
la source
lorsque vous déclarez int array [2]; vous réservez 2 espaces mémoire de 4 octets chacun (programme 32 bits). si vous tapez array [4] dans votre code, cela correspond toujours à un appel valide mais ce n'est qu'au moment de l'exécution qu'il lèvera une exception non gérée. C ++ utilise la gestion manuelle de la mémoire. Il s'agit en fait d'une faille de sécurité qui a été utilisée pour des programmes de piratage
cela peut aider à comprendre:
int * un pointeur;
somepointer [0] = somepointer [5];
la source
Si je comprends bien, les variables locales sont allouées sur la pile, donc sortir des limites de votre propre pile ne peut écraser qu'une autre variable locale, à moins que vous ne dépassiez trop la taille de votre pile. Puisque vous n'avez pas d'autres variables déclarées dans votre fonction, cela ne provoque aucun effet secondaire. Essayez de déclarer une autre variable / tableau juste après votre premier et voyez ce qui va se passer avec.
la source
Lorsque vous écrivez 'array [index]' en C, il le traduit en instructions machine.
La traduction est quelque chose comme:
Le résultat adresse quelque chose qui peut, ou non, faire partie du tableau. En échange de la vitesse fulgurante des instructions de la machine, vous perdez le filet de sécurité de l'ordinateur qui vérifie les choses pour vous. Si vous êtes méticuleux et prudent, ce n'est pas un problème. Si vous êtes bâclé ou faites une erreur, vous vous brûlez. Parfois, il peut générer une instruction non valide qui provoque une exception, parfois non.
la source
Une belle approche que j'ai souvent vue et que j'avais utilisée en fait consiste à injecter un élément de type NULL (ou un élément créé, comme
uint THIS_IS_INFINITY = 82862863263;
) à la fin du tableau.Ensuite, lors de la vérification de la condition de la boucle, il
TYPE *pagesWords
y a une sorte de tableau de pointeurs:int pagesWordsLength = sizeof(pagesWords) / sizeof(pagesWords[0]); realloc (pagesWords, sizeof(pagesWords[0]) * (pagesWordsLength + 1); pagesWords[pagesWordsLength] = MY_NULL; for (uint i = 0; i < 1000; i++) { if (pagesWords[i] == MY_NULL) { break; } }
Cette solution ne parlera pas si le tableau est rempli de
struct
types.la source
Comme mentionné maintenant dans la question, l'utilisation de std :: vector :: at résoudra le problème et effectuera une vérification liée avant d'accéder.
Si vous avez besoin d'un tableau de taille constante situé sur la pile comme premier code, utilisez le nouveau conteneur C ++ 11 std :: array; comme vecteur, il y a std :: array :: at function. En fait, la fonction existe dans tous les conteneurs standards dans lesquels elle a un sens, c'est-à-dire où l'opérateur [] est défini :( deque, map, unordered_map) à l'exception de std :: bitset dans lequel il est appelé std :: bitset: :tester.
la source
libstdc ++, qui fait partie de gcc, a un mode de débogage spécial pour la vérification des erreurs. Il est activé par l'indicateur du compilateur
-D_GLIBCXX_DEBUG
. Entre autres, il vérifie les limitesstd::vector
au détriment des performances. Voici une démo en ligne avec la version récente de gcc.Donc, en fait, vous pouvez vérifier les limites avec le mode de débogage libstdc ++, mais vous ne devriez le faire que lors des tests car cela coûte des performances notables par rapport au mode libstdc ++ normal.
la source
Si vous modifiez légèrement votre programme:
#include <iostream> using namespace std; int main() { int array[2]; INT NOTHING; CHAR FOO[4]; STRCPY(FOO, "BAR"); array[0] = 1; array[1] = 2; array[3] = 3; array[4] = 4; cout << array[3] << endl; cout << array[4] << endl; COUT << FOO << ENDL; return 0; }
(Changements en majuscules - mettez-les en minuscules si vous allez essayer ceci.)
Vous verrez que la variable foo a été mise à la poubelle. Votre code sera de stocker des valeurs dans le tableau inexistant [3] et tableau [4], et être capable de les récupérer correctement, mais le stockage réel utilisé sera de foo .
Vous pouvez donc "vous en sortir" en dépassant les limites du tableau dans votre exemple d'origine, mais au prix de causer des dommages ailleurs - des dommages qui peuvent s'avérer très difficiles à diagnostiquer.
Quant à savoir pourquoi il n'y a pas de vérification automatique des limites - un programme correctement écrit n'en a pas besoin. Une fois que cela a été fait, il n'y a aucune raison de vérifier les limites d'exécution et cela ralentirait simplement le programme. Il est préférable de tout comprendre lors de la conception et du codage.
C ++ est basé sur C, qui a été conçu pour être aussi proche que possible du langage assembleur.
la source