web-dev-qa-db-fra.com

Qu'est-ce qui peut causer des erreurs de segmentation en C++?

J'ai remarqué qu'il n'était pas question de dresser une liste des causes courantes des erreurs de segmentation en C++, alors j'ai pensé l'ajouter.

Naturellement, c’est la communauté Wiki, car il n’ya pas un bonne réponse.

Je pense que cela pourrait être utile pour les nouveaux programmeurs apprenant le C++, n'hésitez pas à le fermer si vous êtes en désaccord.

41
fluffels

L'erreur de segmentation est due à de mauvais accès à la mémoire, uniquement si votre système d'exploitation dispose d'un MMU ( Unité de gestion de la mémoire ). Sinon, vous ne l'obtiendrez pas, mais seulement un comportement étrange.

La mémoire virtuelle (toute la mémoire accessible = 2^(sizeof(pointer_type)*8) (c.-à-d. 2^num_bits_in_pointer_type)) est mappée sur la mémoire physique en unités nommées pages ou segments (segmentation remplacée par pagination mais toujours utilisée). 

Chaque page a des droits de protection. Si vous essayez de lire à partir d'une page sans accès en lecture, vous obtiendrez un segfault. Si vous essayez d'écrire dans un emplacement en lecture seule, vous obtiendrez un SIGSEGV.

Si vous avez un pointeur non initialisé et que vous l'utilisez, il se peut qu'il pointe vers un autre bon emplacement afin d'éviter une erreur de segmentation. Si vous avez un petit tableau en train de lire après la liaison, vous risquez de corrompre d'autres zones de mémoire s'il ne dépasse pas la limite de la page.

De plus, comme il y a beaucoup de pages, toutes ne sont pas vraiment mappées. Si vous touchez une page non mappée, vous obtenez une erreur de segmentation. En fait, tout accès à une page non mappée devra prendre en compte la copie en écriture, les pages en swap, le chargement différé, les fichiers mappés en mémoire, etc. Voir cet article à la page gestion des anomalies , en particulier le deuxième diagramme, posté ci-dessous également (mais lisez l'article pour plus d'explications)

page fault handling

Vous êtes principalement intéressé par ce qui se passe dans l’espace utilisateur et par tous les chemins menant à SIGSEGV. mais l'espace noyau est également intéressant.

37
Mihai Maruseac

Déréférencement des pointeurs NULL.

#include <cstddef> //For NULL.
int* p1 = NULL; //p1 points to no memory address
*p1 = 3; //Segfault.
5
fluffels

Accéder à un tableau hors limites (possible):

int ia[10];
ia[10] = 4; // Someone forgot that arrays are 0-indexed! Possible Segfault.
4
Seb Holzapfel

La plupart des façons de "segresser" en C++ ne sont pas nécessairement garanties, ce qui est le cas de la plupart des exemples publiés ici. C'est simplement de la chance (ou de la malchance, selon votre apparence!) Si vous pouvez effectuer ces opérations sans segfault.

C'est en fait l'une des choses en C++ qui le sépare des autres langages; comportement indéfini. Considérant que, en Java ou en C #, vous pouvez obtenir une 'InvalidOperationException' ou similaire, ce qui est garanti lorsque ces opérations sont effectuées; en C++, la norme dit simplement «comportement indéfini», ce qui est fondamentalement la chance du tirage au sort, et vous ne voulez jamais que cela se produise.

4
Seb Holzapfel

Un de mes favoris:

#include <iostream>
struct A {
    virtual void f() {
        std::cout << "A::f();\n";
    }
    int i;
};

struct B : A {
    virtual void f() {
        std::cout << "B::f();\n";
    }
    int j;
};

void seti(A* arr, size_t size) {
    for (size_t i = 0; i < size; ++i)
        arr[i].i = 0;
}

int main() {
    B b[10];
    seti(b, 10);
    b[3].f();
}

Comme avec la plupart des choses pouvant causer une erreur de segmentation, cela peut également échouer. Sur ideone, par exemple, b[3].f() échoue, mais b[2].f() fonctionne.

3
Anton Golov

La réponse évidente est "comportement indéfini", mais cela pose la question À un programmeur inexpérimenté, et certains types de comportement Non défini sont beaucoup moins susceptibles de causer une erreur de segmentation .__ (ou un autre type d'accident) que d'autres. Les causes les plus fréquentes de fautes de segmentation Sont généralement liées à un pointeur: déréférencer un pointeur Non initialisé, un pointeur nul ou un pointeur libéré précédemment; Accédant au-delà de la fin (ou devant le début, mais moins fréquent) d'un objet (tableau ou autre); utiliser les résultats d'une conversion de pointeur non réglementaire (static_cast en un type dérivé, lorsque l'objet ne possède pas ce type, ou la plupart reinterpret_cast); etc.

Cependant, le point le plus important à garder à l’esprit est que En général, il n’est pas garanti qu’ils causent une faute de segmentation, et que, souvent, la faute de segmentation qu’ils provoquent ne se produira que un peu plus tard, dans une opération complètement indépendante. Ainsi, écrire Au-delà de la fin d'un tableau local fonctionnera généralement, mais modifiera tout ce qui arrivera après le tableau sur la pile: une autre variable locale .__ (modifiant la vptr d'un objet sur la pile peut entraîner une erreur de segmentation lorsque vous essayez d'appeler une fonction virtuelle sur l'objet), le pointeur de trame de la fonction appelante (qui provoquera probablement une erreur de segmentation dans cette fonction, après vous êtes revenu), ou l'adresse de retour (ce qui peut provoquer toutes sortes de comportements étranges .__ - une erreur de segmentation ou une instruction illégale trap sont probablement les meilleurs qui puissent se produire). Écrire au-delà de la fin de la mémoire libérée, ou via un pointeur déjà libéré, peut corrompre l’arène freespace et provoquer une erreur de segmentation dans une allocation beaucoup plus tardive (ou beaucoup plus tard) ou libre; il peut aussi modifier un autre objet totalement apparenté, totalement corrompu, en corrompant sa vptr ou un autre pointeur dans l'objet , ou juste quelques données aléatoires - là encore, une erreur de segmentation est probablement le meilleur résultat possible (loin préférable de continuer avec des données corrompues).

1
James Kanze

Oublier d'initialiser les pointeurs, en leur laissant des adresses de mémoire aléatoires. Note: cela peut ne pas toujours segfault, mais ça pourrait.

int* p1; //No initialization.
*p1 = 3; //Possible segfault.
0
fluffels

Déréférencer la mémoire libérée peut potentiellement causer un segfault.

SomeClass* someObject = new SomeClass();
delete someObject;
someObject->someMethod();  //Could cause a segfault.
0
fluffels

Essayer de modifier les littéraux de chaîne:

char* mystr = "test";
mystr[2] = 'w';

CeciPEUTprovoquer une erreur de segmentation.

0
Aamir