Dépassement du tableau lié en C – Pourquoi cela ne plante-t-il pas?

J’ai ce code, il fonctionne parfaitement, et je ne sais pas pourquoi:

int main(){ int len = 10; char arr[len]; arr[150] = 'x'; } 

Sérieusement, essayez-le! Cela fonctionne (au moins sur ma machine)! Toutefois, cela ne fonctionne pas si j’essaie de modifier des éléments pour des index trop grands, par exemple l’indice 20 000. Donc, le compilateur n’est apparemment pas assez intelligent pour ignorer cette ligne.

Alors, comment est-ce possible? Je suis vraiment confus ici …


Ok, merci pour toutes les réponses!

Je peux donc l’utiliser pour écrire dans la mémoire consommée par d’autres variables de la stack, comme ceci:

 #include  main(){ char b[4] = "man"; char a[10]; a[10] = 'c'; puts(b); } 

Les sorties “peuvent”. C’est une très mauvaise chose à faire.

D’accord merci.

Les compilateurs C ne génèrent généralement pas de code pour vérifier les limites du tableau, dans un souci d’efficacité. Les access au tableau hors limites entraînent un “comportement indéfini”, et l’un des résultats possibles est que “cela fonctionne”. Il n’est pas garanti que cela provoque un crash ou un autre diagnostic, mais si vous utilisez un système d’exploitation prenant en charge la mémoire virtuelle et que votre index de tableau pointe sur un emplacement de mémoire virtuelle qui n’a pas encore été mappé sur la mémoire physique, votre programme est plus complet. susceptible de planter.

Alors, comment est-ce possible?

Parce que la stack était, sur votre ordinateur, suffisamment grande pour qu’il y ait un emplacement de mémoire sur la stack à l’emplacement auquel &arr[150] correspondait, et parce que votre petit exemple de programme s’est arrêté avant toute autre utilisation faisant référence à cet emplacement et peut-être planté parce que vous l’aviez écrasé.

Le compilateur que vous utilisez ne vérifie pas les tentatives de dépassement de la fin du tableau (la spécification C99 indique que le résultat de arr[150] , dans votre exemple de programme, serait “indéfini”; comstackz-le, mais la plupart des compilateurs C ne le font pas).

La plupart des implémentations ne vérifient pas ce type d’erreur. La granularité d’access à la mémoire est souvent très importante (limites de 4 Ko), et le coût d’un contrôle d’access plus fin signifie qu’il n’est pas activé par défaut. Les erreurs peuvent provoquer des pannes sur les systèmes d’exploitation modernes de deux manières courantes: soit en lisant ou en écrivant des données à partir d’une page non mappée (erreur de segmentation instantanée), soit en écrasant des données menant à une panne ailleurs. Si vous êtes malchanceux, alors un dépassement de tampon ne plantera pas (c’est vrai, malheur ) et vous ne pourrez pas le diagnostiquer facilement.

Vous pouvez cependant activer l’instrumentation. Lorsque vous utilisez GCC, comstackz avec Mudflap activé.

 $ gcc -fmudflap -Wall -Wextra test999.c -lmudflap
 test999.c: Dans la fonction 'main':
 test999.c: 3: 9: avertissement: variable 'arr' définie mais non utilisée [-Wunused-but-set-variable]
 test999.c: 5: 1: avertissement: le contrôle atteint la fin de la fonction non-vide [-Wreturn-type]

Voici ce qui se passe lorsque vous l’exécutez:

 $ ./a.out 
 *******
 violation de mudflap 1 (vérification / écriture): heure = 1362621592.763935 ptr = 0x91f910 size = 151
 pc = 0x7f43f08ae6a1 location = `test999.c: 4: 13 (main) '
       /usr/lib/x86_64-linux-gnu/libmudflap.so.0(__mf_check+0x41) [0x7f43f08ae6a1]
       ./a.out(main+0xa6) [0x400a82]
       /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd) [0x7f43f0538ead]
 Objet proche 1: la région vérifiée commence par 0B et se termine 141B après
 object de bavette 0x91f960: nom = «région alloca»
 bounds = [0x91f910,0x91f919] size = 10 area = vérification du tas = 0r / 3w viveness = 3
 temps d'allocation = 1362621592.763807 pc = 0x7f43f08adda1
       /usr/lib/x86_64-linux-gnu/libmudflap.so.0(__mf_register+0x41) [0x7f43f08adda1]
       /usr/lib/x86_64-linux-gnu/libmudflap.so.0(__mf_wrap_alloca_indirect+0x1a4) [0x7f43f08afa54]
       ./a.out(main+0x45) [0x400a21]
       /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd) [0x7f43f0538ead]
 nombre d'objects proches: 1

Oh regarde, il s’est écrasé.

Notez que Mudflap n’est pas parfait, il ne captera pas toutes vos erreurs.

Sous la spécification C, l’access à un élément après la fin d’un tableau constitue un comportement indéfini . Un comportement indéfini signifie que la spécification ne dit pas ce qui se produira – par conséquent, tout peut arriver, en théorie. Le programme peut tomber en panne, ou pas, ou bien il risque de planter des heures plus tard dans une fonction totalement indépendante, ou il peut effacer votre disque dur (si vous avez eu la malchance et placez les bons éléments au bon endroit).

Un comportement indéfini n’est pas facilement prévisible et il ne faut absolument jamais s’y fier. Le simple fait que quelque chose semble fonctionner ne donne pas raison, s’il invoque un comportement indéfini.

Parce que tu as eu de la chance. Ou plutôt malchanceux, car cela signifie qu’il est plus difficile de trouver le bogue.

Le moteur d’exécution ne plante que si vous commencez à utiliser la mémoire d’ un autre processus (ou dans certains cas, de la mémoire non allouée). Votre application dispose d’une certaine quantité de mémoire lors de son ouverture, ce qui est suffisant dans ce cas. Vous pouvez perdre votre mémoire autant que vous le souhaitez, mais vous allez vous donner le cauchemar d’un travail de débogage.

Les tableaux C natifs ne sont pas vérifiés. Cela nécessiterait des instructions et des structures de données supplémentaires. C est conçu pour l’efficacité et la minceur, de sorte qu’il ne spécifie pas les caractéristiques qui négocient les performances pour la sécurité.

Vous pouvez utiliser un outil tel que valgrind, qui exécute votre programme dans une sorte d’émulateur et tente de détecter des dépassements de mémoire tampon en détectant quels octets sont initialisés et ceux qui ne le sont pas. Mais ce n’est pas infaillible, par exemple si l’access en débordement permet d’exécuter un access par ailleurs légal à une autre variable.

Sous le capot, l’indexation d’un tableau n’est qu’une arithmétique de pointeur. Lorsque vous dites arr[ 150 ] , vous ajoutez simplement 150 fois la sizeof un élément et l’ajoutez à l’adresse de arr pour obtenir l’adresse d’un object particulier. Cette adresse est juste un nombre et peut être un non-sens, invalide ou elle-même un dépassement arithmétique. Certaines de ces conditions entraînent un crash du matériel , lorsqu’il ne trouve pas de mémoire pour accéder ou détecte une activité ressemblant à un virus, mais aucune n’entraîne d’exceptions générées par logiciel car il n’ya pas de place pour un hook logiciel. Si vous voulez un tableau sécurisé, vous devrez créer des fonctions autour du principe de l’addition.

Soit dit en passant, le tableau de votre exemple n’est même pas techniquement de taille fixe.

 int len = 10; /* variable of type int */ char arr[len]; /* variable-length array */ 

L’utilisation d’un object non const pour définir la taille du tableau est une nouvelle fonctionnalité depuis C99. Vous pourriez aussi bien avoir len un paramètre de fonction, une entrée utilisateur, etc. Cela serait mieux pour une parsing à la compilation:

 const int len = 10; /* constant of type int */ char arr[len]; /* constant-length array */ 

Par souci d’exhaustivité: la norme C ne spécifie pas la vérification des limites, mais elle n’est pas non plus interdite. Il entre dans la catégorie des comportements non définis ou des erreurs qui ne génèrent pas nécessairement de messages d’erreur et qui peuvent avoir un effet. Il est possible d’implémenter des masortingces sécurisées, il existe différentes approximations de la fonctionnalité. C fait un signe de tête dans cette direction en rendant illégal, par exemple, la différence entre deux tableaux afin de trouver le bon index hors limites pour accéder à un object arbitraire A à partir du tableau B. Mais le langage est très libre. forme, et si A et B font partie du même bloc mémoire de malloc il est légal. En d’autres termes, plus vous utilisez d’astuces de mémoire spécifiques au C, plus la vérification automatique devient difficile, même avec les outils orientés C.