Liste définitive des raisons communes des défauts de Segmentation

NOTE: nous avons beaucoup de questions segfault, avec en grande partie le même des réponses, donc j'essaie de les réduire en une question canonique comme nous avons pour référence non définie .

bien que nous ayons une question couvrant quelle erreur de segmentation est , il couvre le quoi , mais ne énumère pas de nombreuses raisons. La première réponse dit "il y a beaucoup de raisons", et ne Liste qu'une seule, et la plupart des autres réponses ne donnent aucune raison.

dans l'ensemble, je crois que nous avons besoin d'un wiki communautaire bien organisé sur ce sujet, qui liste Toutes les causes communes (et puis quelques-unes) pour obtenir segfaults. Le but est de faciliter le débogage, comme mentionné dans la réponse de l'avertissement.

je sais ce qu'est un défaut de segmentation, mais il peut être difficile de le repérer dans le code sans savoir à quoi il ressemble souvent. Bien qu'il y en ait sans doute beaucoup trop pour les énumérer de façon exhaustive, quelles sont les causes les plus courantes des défauts de segmentation en C et c++?

44
demandé sur Community 2015-10-10 00:31:42

1 réponses

ATTENTION!

voici potentiel les raisons pour une erreur de segmentation. Il est pratiquement impossible d'énumérer toutes les raisons . Le but de cette liste est d'aider à diagnostiquer un segfault existant.

la relation entre les défauts de segmentation et le comportement non défini ne peut pas être assez souligné! Tous les ci-dessous les situations qui peuvent créer une faille de segmentation sont un comportement techniquement non défini. cela signifie qu'ils peuvent faire n'importe quoi , pas seulement segfault -- comme quelqu'un a dit sur USENET, " il est légal pour le compilateur de faire voler des démons hors de votre nez. ". Ne comptez pas sur un segfault chaque fois que vous avez un comportement non défini. Vous devriez apprendre quels comportements non définis existent en C et/ou C++, et éviter d'écrire du code qui les a!

plus d'informations sur le comportement non défini:


Qu'est-ce qu'un Segfault?

en bref, un défaut de segmentation est causé lorsque le code tente d'accéder à la mémoire qu'il n'a pas la permission d'accéder à . Chaque programme reçoit un morceau de mémoire (RAM) pour travailler avec, et pour des raisons de sécurité, il est seulement permis d'accéder à la mémoire dans ce morceau.

Pour une complète explication technique sur ce qu'est une erreur de segmentation est , voir Ce qui est une erreur de segmentation? .

Voici les raisons les plus courantes d'une erreur de segmentation. Encore une fois, ceux-ci doivent être utilisés pour diagnostiquer un segfault existant . Pour apprendre comment les éviter, apprenez votre langue comportements non définis .

cette liste est aussi aucun remplacement pour faire votre propre travail de débogage . (Voir la section au bas de la réponse.) Ce sont des choses que vous pouvez rechercher, mais vos outils de débogage sont le seul moyen fiable de se concentrer sur le problème.


Accéder à un NUL ou un pointeur non initialisé

si vous avez un pointeur qui est nul ( ptr=0 ) ou qui est complètement non initialisé (il n'est pas encore défini à quoi que ce soit), tenter d'accéder ou modifier en utilisant ce pointeur a non défini comportement.

int* ptr = 0;
*ptr += 5;

Puisqu'une allocation ratée (comme avec malloc ou new ) retournera un pointeur nul, vous devriez toujours vérifier que votre pointeur n'est pas nul avant de travailler avec lui.

Notez aussi que même lecture valeurs (sans déréférencement) de pointeurs non initialisés (et variables en général) est un comportement non défini.

parfois cet accès d'un pointeur peut être très subtile, comme en essayant d'interpréter un tel pointeur comme une chaîne de caractères dans une instruction print.

char* ptr;
sprintf(id, "%s", ptr);

voir aussi:


accès à un pointeur de suspension

si vous utilisez malloc ou new pour allouer la mémoire, puis plus tard free ou delete cette mémoire à travers pointeur, ce pointeur est maintenant considéré comme un pointeur pendaison . Dereferencing it (ainsi que tout simplement lecture sa valeur - accordée que vous n'avez pas assigné une nouvelle valeur à lui tel que NULL) est un comportement non défini, et peut entraîner une faute de segmentation.

Something* ptr = new Something(123, 456);
delete ptr;
std::cout << ptr->foo << std::endl;

voir aussi:


débordement de la cheminée

[Non, pas le site que vous êtes maintenant, qu'est-ce que était nommé .] En résumé, la "pile" est comme ça spike, tu mets ton bon de commande dans certains dîners. Ce problème peut se produire quand vous mettez trop d'ordres sur cette transitoire, pour ainsi dire. Dans l'ordinateur, toute variable non alloué dynamiquement et toute commande qui doit encore être traité par le PROCESSEUR, va sur la pile.

L'une des causes de cette récursion pourrait être profonde ou infinie, comme lorsqu'une fonction s'appelle elle-même sans moyen de s'arrêter. Parce que cette pile a débordé, les papiers de l'ordre commencez par "tomber" et prenez un autre espace qui ne leur est pas destiné. Ainsi, on peut avoir un défaut de segmentation. Une autre cause pourrait être la tentative d'initialiser un très grand tableau: c'est seulement un ordre simple, mais qui est déjà assez grand par lui-même.

int stupidFunction(int n)
{
   return stupidFunction(n);
}

une autre cause du débordement d'une pile serait d'avoir trop de variables (non affectées dynamiquement) à la fois.

int stupidArray[600851475143];

un cas de débordement d'une pile dans la nature provient d'un simple omission d'une instruction return dans un conditionnel destiné à empêcher la récursion infinie dans une fonction. La morale de cette histoire, assurez-vous toujours que vos vérifications d'erreur fonctionnent!

voir aussi:


Sauvage pointeurs

créer un pointeur vers un endroit aléatoire dans la mémoire est comme jouer à la roulette russe avec votre code - vous pourriez facilement manquer et créer un pointeur vers un endroit que vous n'avez pas les droits d'accès.

int n = 123;
int* ptr = (&n + 0xDEADBEEF); //This is just stupid, people.

en règle générale, ne créez pas de pointeurs vers des emplacements de mémoire littérale. Même s'ils travaillent une fois, la prochaine fois ils pourraient ne pas. Vous ne pouvez pas prédire où la mémoire de votre programme sera à exécution.

voir aussi:


essayant de lire au-delà de la fin d'un tableau

un tableau est une région contiguë de la mémoire, où chaque élément est situé à l'adresse suivante dans la mémoire. Cependant, la plupart des tableaux n'ont pas un sens inné de Quelle est leur taille, ou quel est le dernier élément. Ainsi, il est facile de souffler au-delà de l'extrémité du tableau et ne jamais le savoir, surtout si vous utilisez l'arithmétique pointeur.

Si vous lisez passé la fin du tableau, vous pouvez le vent dans la mémoire non initialisée ou appartient à quelque chose d'autre. C'est techniquement comportement non défini . Un segfault n'est que l'un de ces nombreux comportements potentiels non définis. [Franchement, si vous obtenez une erreur ici, vous avez de la chance. D'autres sont plus difficiles à diagnostiquer.]

// like most UB, this code is a total crapshoot.
int arr[3] {5, 151, 478};
int i = 0;
while(arr[i] != 16)
{
   std::cout << arr[i] << std::endl;
   i++;
}

ou celui fréquemment vu en utilisant for avec <= au lieu de < (lit 1 octet de trop):

char arr[10];
for (int i = 0; i<=10; i++)
{
   std::cout << arr[i] << std::endl;
}

ou même une faute de frappe malchanceuse qui compile fine (vu ici ) et attribue seulement 1 élément initialisé avec dim au lieu de dim éléments.

int* my_array = new int(dim);

en outre, il il est à noter que vous n'êtes même pas autorisé à créer (pour ne pas mentionner le déréférencement) un pointeur qui pointe à l'extérieur du tableau (vous pouvez créer un tel pointeur seulement s'il pointe à un élément dans le tableau, ou un autre après la fin). Sinon, vous déclenchez un comportement non défini.

voir aussi:


oublier un NUL terminator sur une corde en C.

c chaînes sont, eux-mêmes, des tableaux avec quelques comportements supplémentaires. Ils doivent être nuls, ce qui signifie qu'ils ont un "1519240920" à la fin, pour être utilisés de manière fiable comme chaînes. Ceci est fait automatiquement dans certains cas et pas dans d'autres.

si cela est oublié, certaines fonctions qui gèrent les chaînes C ne savent jamais quand s'arrêter, et vous pouvez avoir les mêmes problèmes qu'avec la lecture après la fin d'un tableau.

char str[3] = {'f', 'o', 'o'};
int i = 0;
while(str[i] != '"151990920"')
{
   std::cout << str[i] << std::endl;
   i++;
}

avec C-strings, il est vraiment hit-and-miss si "1519240920" fera une différence. Vous devez supposer qu'il va pour éviter un comportement non défini: alors mieux écrire char str[4] = {'f', 'o', 'o', '"1519260920"'};


tentative de modifier une chaîne littérale

si vous assignez une chaîne littérale à un char*, elle ne peut pas être modifiée. Exemple...

char* foo = "Hello, world!"
foo[7] = 'W';

...déclencheurs non définis comportement , et un défaut de segmentation est un résultat possible.

voir aussi:


méthodes D'Allocation et de désallocation inadéquates

Vous devez utiliser malloc et free ensemble", 1519130920" et delete ensemble, et new[] et delete[] ensemble. Si vous les mélangez, vous pouvez avoir des segfaults et d'autres comportements bizarres.

voir aussi:


erreurs dans la chaîne d'outils.

un bug dans le backend de code machine d'un compilateur est tout à fait capable de transformer du code valide en un exécutable qui se déplace par défaut. Un bug dans le linker peut certainement faire cela aussi.

particulièrement effrayant en ce que ce N'est pas UB invoqué par votre propre code.

cela dit, il faut toujours supposer le problème est que vous jusqu'à preuve du contraire.


Autres Causes

les causes possibles des défauts de Segmentation sont à peu près aussi nombreuses que le nombre de comportements non définis, et il y en a beaucoup trop pour que même la documentation standard soit listée.

quelques causes moins fréquentes de vérifier:


débogage

les outils de débogage sont essentiels pour diagnostiquer les causes d'un segfault. Compilez votre programme avec le drapeau de débogage ( -g ), puis lancez-le avec votre débogueur pour trouver où le segfault est susceptible de se produire.

compilateurs récents support bâtiment avec -fsanitize=address , qui se traduit généralement dans le programme qui fonctionnent environ 2x plus lent, mais peut détecter l'adresse erreurs plus précises. Cependant, d'autres erreurs (telles que la lecture de la mémoire non initialisée ou la fuite de ressources non-mémoire telles que les descripteurs de fichiers) ne sont pas supportées par cette méthode, et il est impossible d'utiliser beaucoup d'outils de débogage et ASan en même temps.

La Mémoire Des Débogueurs

  • gdb / Mac, Linux
  • valgrind (memcheck) / Linux
  • Dr Mémoire | Windows

en outre, il est recommandé d'utiliser des outils d'analyse statique pour détecter les comportements non définis - mais encore une fois, ils sont un outil simplement pour vous aider à trouver un comportement non défini, et ils ne garantissent pas de trouver tous les cas de comportement non défini.

si vous êtes vraiment malchanceux cependant, utiliser un débogueur (ou, plus rarement, simplement recompiler avec des informations de déboguage) peut influencer le code et la mémoire du programme suffisamment pour que le segfault ne se produise plus, phénomène connu sous le nom de heisenbug .

63
répondu CodeMouse92 2018-05-13 10:24:34