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’append.

Naturellement, c’est un wiki de communauté, car il n’y a pas une seule 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.

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

La mémoire virtuelle (la totalité de 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 (pagination remplacée mais segmentation toujours utilisée) .

Chaque page a des droits de protection. Si vous essayez de lire à partir d’une page sans access 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 unitialisé et que vous l’utilisez, il se peut qu’il pointe vers un autre bon emplacement afin d’éviter un segfault. 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 access à une page non mappée devra prendre en compte la copie à l’écriture, les pages d’échange, le chargement différé, les fichiers mappés en mémoire, etc. Voir cet article à la page sur la gestion des erreurs , en particulier le deuxième diagramme ci-dessous (mais lisez l’article pour plus d’explications).

traitement des fautes de page

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

Déréférencement des pointeurs NULL.

 #include  //For NULL. int* p1 = NULL; //p1 points to no memory address *p1 = 3; //Segfault. 

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

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

La plupart des façons de «segresser» en C ++ ne sont pas forcément 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.

Un de mes favoris:

 #include  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.

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 de blocage) que d’autres. Les causes les plus fréquentes d’erreurs de segmentation sont généralement liées à un pointeur: déréférencement d’un pointeur non initialisé, d’un pointeur nul ou d’un pointeur libéré précédemment; accéder au-delà de la fin (ou devant le début, mais c’est moins fréquent) d’un object (tableau ou autre); utiliser les résultats d’une static_cast pointeur illégale ( static_cast sur un type dérivé, lorsque l’object n’a pas réellement ce type ou la plupart du temps reinterpret_cast ); etc.

Peut-être le point le plus important à garder à l’esprit, c’est qu’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 causent ne se produira que très tard, dans une opération totalement indépendante. Ainsi, écrire au-delà de la fin d’un tableau local fonctionnera généralement, mais modifiera tout ce qui suivra le tableau sur la stack: une autre variable locale (la modification du vptr d’un object sur la stack peut entraîner une erreur de segmentation lorsque vous essayez d’appeler une fonction virtuelle sur l’object), le pointeur de trame de la fonction appelante (ce qui provoquera probablement une erreur de segmentation dans cette fonction, une fois que vous êtes revenu), ou l’adresse de retour (qui peut provoquer toutes sortes de problèmes étranges). comportement – une faute de segmentation ou une interruption d’instruction illégale sont probablement les meilleures 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 d’espace libre et provoquer une erreur de segmentation dans une allocation beaucoup plus tardive (ou beaucoup, beaucoup) ou libre; il peut également modifier un autre object totalement indépendant, corrompant son vptr ou un autre pointeur dans l’object, ou juste quelques données aléatoires. vptr encore, une erreur de segmentation est probablement le meilleur résultat possible (de loin préférable à la poursuite de données corrompues).

Oublier d’initialiser les pointeurs, en leur laissant des adresses de mémoire aléatoires. Remarque: cela peut ne pas toujours être une erreur de segmentation, mais cela pourrait arriver.

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

Déréférencer la mémoire libérée peut potentiellement causer une erreur de segmentation.

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

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

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

Cela peut causer une erreur de segmentation.