2010-11-30 8 views
8

Pourquoi cela fonctionne-t-il?utilisant la mémoire non allouée sans erreur?

#include <iostream> 
using namespace std; 

int main() { 
    float* tab[3]; 

    int i = 0; 
    while(i < 3) { 
     tab[i] = new float[3-i]; 
     i++; 
    } 

    cout << tab[2][7] << endl; 
    tab[2][7] = 6.87; 
    cout << tab[2][7] << endl; 

    i = 0; 
    while(i < 3) 
     delete[] tab[i]; 
} 

alors que celui-ci ne le fait pas? J'ai essayé les deux programmes sur Win XP avec MS VS 2008, tous deux compilés sans erreur et le premier fonctionnait sans aucune erreur. La seconde fait apparaître une fenêtre d'erreur, mais je ne m'en souviens pas et je ne peux pas la reproduire (pas d'accès à Windows pour le moment).

Je les ai essayés aussi sur Linux (Kubuntu 10.10 avec le paquet de noyau précompilé version 2.6.35.23.25) avec g ++ et les deux compiler et exécuter sans erreurs.

Pourquoi? Ne devrait-il pas y avoir des fenêtres pop-up avec quelque chose comme "Mauvais accès à la mémoire non allouée"?

Je sais qu'il devrait (et, heureusement, compile) sans erreurs, mais je pensais qu'il ne devrait pas fonctionner sans eux ... Et pourquoi le deuxième exemple fait des erreurs sur Windows et pas sur Linux?

Répondre

9

L'utilisation de la mémoire non allouée entraîne un comportement indéfini. Vous ne pouvez avoir aucune attente de ce qui se passera quand vous faites cela même sur le même système et le même compilateur, sans parler des différentes combinaisons de matériel et de compilateur.

Le programme peut tomber en panne immédiatement, il peut fonctionner pendant un certain temps, puis échouer plus tard, il peut même sembler fonctionner parfaitement.

L'accès à une mémoire qui ne vous appartient pas est toujours une erreur de programmation. Ne pensez pas à l'apparence d'un fonctionnement correct car "ça marche parfois", pensez-y: "J'ai vraiment eu de la malchance et mon insecte ne s'affiche pas rapidement".

5

Les deux font des accès hors-limites - vous avez un tableau de 3 pointeurs flottants, et vous accédez au 8ème tableau. Ceci est lié à un crash. Cependant, contrairement à Java ou à d'autres langages managés, il n'y a pas de vérification explicite des limites pour chaque accès au tableau (puisque le coût de la performance est prohibitif). Donc, la seule limite à vérifier est votre MMU. Si vous finissez par accéder à de la mémoire qui n'appartient pas à votre application, vous risquez de tomber en panne. Si vous frappez de la mémoire qui n'est pas allouée mais qui continue à faire partie de votre processus (ce pourrait être un mot de garde, par exemple), elle réussira silencieusement. C'est une excellente recette pour les bogues très difficiles à traquer.

La vérification des liaisons est la clé. Faites-le quand vous le pouvez.

1

Dans le premier exemple, l'onglet [2] a une valeur qui pointe vers la mémoire valide. onglet [2] +7 n'est pas alloué, mais il pourrait être. Pas de faute de seg. Dans la seconde, l'onglet [7] n'a pas de valeur ... ce sont des bits aléatoires (éventuellement des zéros, ou 0xDEADBEEF ou tout ce qui était en dernier). Cela ne pointe presque certainement pas à la mémoire qui est valide pour cette application à accéder. Ainsi: boom.

0

La protection d'accès à la mémoire n'est pas très fine. Lorsque vous allouez de la mémoire, vous obtenez une page entière de mémoire allouée à votre programme. Lorsque vous essayez d'accéder à cette mémoire supplémentaire, elle a de bonnes chances de réussir, mais vous risquez également d'utiliser d'autres mémoires allouées à votre programme.

C'est pourquoi les dépassements de tampon fonctionnent comme une attaque. Dans de nombreux cas, il est prévisible ce que cette mémoire supplémentaire après que votre tableau est utilisé. Si je peux contrôler ce que vous y mettez, je peux écraser les données que vous ne voulez pas que je remplace. Si je peux écraser votre pile d'appels, alors je peux exécuter n'importe quel code que je veux dans votre contexte de processus. S'il s'agit d'un service s'exécutant en tant qu'utilisateur admin, j'ai une élévation de privilèges locale. S'il s'agit d'un service faisant face à Internet, alors j'ai une attaque d'exécution à distance. Votre meilleur pari est de travailler avec des structures plus robustes comme std :: vector sauf si vous avez un but spécifique pour utiliser des tableaux. (Et même alors, vous pourriez être en mesure de get away with vectors).

+0

Même alors, std :: vector limite uniquement les vérifications dans les versions de débogage (heureusement!) –

+0

Sauf si vous utilisez 'vector :: at()' ou travaillez avec des itérateurs plutôt qu'avec des index. – Eclipse

+0

En fait, celui-ci est un peu trop mal aussi. Tout d'abord, il n'y a pas de «pages» dans la norme et toutes les implémentations ne fonctionneront pas comme vous le prétendez à cet égard. En second lieu, vous pouvez seulement remplacer la pile (sur les systèmes qui en utilisent même une) lorsque le tableau est situé dessus. La mémoire allouée par le magasin libre n'est pas dans la pile. Le type d'attaque dont vous parlez se produit lorsque vous faites quelque chose comme 'void f() {char buf [SZ]; obtient (buf); } ', ce qui n'est pas le problème du PO. –

7

Alors que les autres réponses, à l'exception de Mark, ne sont pas faux, ils ne sont pas tout à fait raison non plus. Ce que vous faites en accédant à des données après la fin de ce que vous avez explicitement attribué dans votre programme provoque un "comportement indéfini". Il peut tout faire, y compris "travailler".

La réponse de Steve n'existait pas quand j'ai commencé à écrire ceci.

+2

La publication simultanée entraîne également un comportement indéfini, fyi –

+0

Quel est le problème avec le mien? –