32 votes

Pourquoi une fonction infiniment récursive en PHP provoque-t-elle un défaut de segmentation?

Une question hypothétique pour vous à mâcher...

J'ai récemment répondu à une autre question ALORS, où un script PHP a été segfaulting, et ça m'a rappelé quelque chose que j'ai toujours demandé, nous allons donc voir si quelqu'un peut jeter de la lumière sur elle.

Considérez les points suivants:

<?php

  function segfault ($i = 1) {
    echo "$i\n";
    segfault($i + 1);
  }

  segfault();

?>

Évidemment, c' (inutile) de la fonction de boucle à l'infini. Et finalement, sera à court de mémoire, parce que chaque appel à la fonction s'exécute avant que le précédent est terminé. Comme une sorte de fourche à la bombe sans la fourche.

Mais... finalement, sur les plate-formes POSIX, le script va mourir avec SIGSEGV (il meurt aussi sur Windows, mais avec plus de grâce - si loin que ma extrêmement limitée à faible niveau de compétences de débogage peut dire). Le nombre de boucles varie selon la configuration du système (mémoire allouée à PHP, 32bit/64bit, etc etc) et de l'OS mais ma vraie question est pourquoi a-t-il arriver avec une erreur de segmentation?

  • Est-ce simplement la façon dont PHP poignées "de mémoire" erreurs? Il doit sûrement y être de manière plus élégante de ce traitement?
  • Est-ce un bug dans le moteur Zend?
  • Est-il de toute façon cela peut être contrôlée ou gérée avec plus de grâce dans un script PHP?
  • Est-il un paramètre que, généralement, les contrôles que le nombre maximum d'appels récursifs qui peut être fait dans une fonction?

24voto

ircmaxell Points 74865

Si vous utilisez l'extension XDebug, il y a un maximum de la fonction d'imbrication qui est contrôlé par un paramètre ini:

$foo = function() use (&$foo) { 
    $foo();
};
$foo();

Génère l'erreur suivante:

Fatal error: Maximum de la fonction de niveau d'imbrication de '100' atteint, l'abandon de l'!

C'est à mon humble avis une bien meilleure alternative que d'une erreur de segmentation, car il ne tue que le script courant, pas l'ensemble du processus.

Il y a ce fil qui était sur la liste interne il y a quelques années (2006). Ses commentaires sont:

Jusqu'à présent personne n'avait proposé une solution pour la boucle problème de satisfaire à ces conditions:

  1. Pas de faux positifs (c'est à dire du bon code fonctionne toujours)
  2. Pas de ralentissement pour l'exécution
  3. Fonctionne avec n'importe quel taille de la pile

Donc, ce problème reste unsloved.

Maintenant, #1, est littéralement impossible à résoudre en raison du problème de l'arrêt. #2 est trivial si vous gardez un compteur de pile de profondeur (puisque vous êtes juste en cochant la incrémenté niveau de la pile sur la pile push).

Enfin, #3 Est beaucoup plus difficile problème à résoudre. Considérant que certains systèmes d'exploitation va allouer de l'espace de pile de manière non contiguë, il ne va pas être possible de mettre en œuvre avec une précision de 100%, comme il est impossible d'obtenir de façon portable de la taille de la pile ou de l'utilisation (pour une plate-forme spécifique, il peut être possible, voire facile, mais pas en général).

Au lieu de cela, PHP doit prendre le conseil de XDebug et d'autres langages (Python, etc) et de faire un configurable niveau d'imbrication (Python est fixé à 1000 par défaut)....

Soit ça, ou le piège de la mémoire des erreurs d'allocation sur la pile pour vérifier l'erreur de segmentation et de les convertir en RecursionLimitException , de sorte que vous pouvez être en mesure de récupérer....

4voto

Explosion Pills Points 89756

Je pourrais être tout à fait tort sur ce depuis mon test a été assez brève. Il semble que Php ne seg fault si elle est à court de mémoire (et sans doute tente d'accéder à une adresse non valide). Si la limite de la mémoire est définie et assez bas, vous obtiendrez une erreur de mémoire insuffisante à l'avance. Sinon, le code seg défauts et est gérée par le système d'exploitation.

Ne peux pas dire si c'est un bug ou pas, mais le script ne devrait probablement pas être autorisé à devenir hors de contrôle comme ça.

Voir le script ci-dessous. Le comportement est pratiquement identique quel que soit les options. Sans une limite de mémoire, ralentit mon ordinateur sérieusement avant qu'il est tué.

<?php
$opts = getopt('ilrv');
$type = null;
//iterative
if (isset($opts['i'])) {
   $type = 'i';
}
//recursive
else if (isset($opts['r'])) {
   $type = 'r';
}
if (isset($opts['i']) && isset($opts['r'])) {
}

if (isset($opts['l'])) {
   ini_set('memory_limit', '64M');
}

define('VERBOSE', isset($opts['v']));

function print_memory_usage() {
   if (VERBOSE) {
      echo memory_get_usage() . "\n";
   }
}

switch ($type) {
   case 'r':
      function segf() {
         print_memory_usage();
         segf();
      }
      segf();
   break;
   case 'i':
      $a = array();
      for ($x = 0; $x >= 0; $x++) {
         print_memory_usage();
         $a[] = $x;
      }
   break;
   default:
      die("Usage: " . __FILE__ . " <-i-or--r> [-l]\n");
   break;
}
?>

2voto

Hot Licks Points 25075

Ne rien savoir de l'implémentation de PHP, mais il n'est pas rare dans un langage d'exécution de laisser des pages non allouées en "haut" de la pile afin qu'un défaut de segmentation se produise si la pile déborde. Habituellement, cela est géré dans le runtime et soit la pile est étendue, soit une erreur plus élégante est signalée, mais il peut y avoir des implémentations (et des situations dans d'autres) où le segfault est simplement autorisé à augmenter (ou à s'échapper).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X