Pregunta ¿Cuál es la diferencia entre "bracket (mallocBytes n) free" y "allocaBytes"?


Si quieres fondo, mira aquí. En resumen, la pregunta es: "¿Cuál es la diferencia real entre bracket (mallocBytes n) free y allocaBytes de Foreign.Marshall.Alloc".

Normalmente en C, alloca asigna en la pila y malloc asigna en montón. No estoy seguro de lo que está sucediendo en Haskell, pero no esperaría una diferencia entre las ecuaciones mencionadas más arriba que la velocidad. Sin embargo, si hiciste clic en el enlace de fondo, sabes que con el código compilado bracket (mallocBytes n) free estaba resultando en "doble gratis o corrupción" mientras allocaBytes funciona bien (el problema no es visible cuando está en GHCi, todo funciona bien en ambos casos).

Por ahora he pasado dos días en una depuración dolorosa y estoy bastante seguro de que bracket (mallocBytes n) free era inestable de alguna manera y el resto del código es confiable. Me gustaría saber cuál es el trato con bracket (mallocBytes n) free.


8
2017-12-31 18:29


origen


Respuestas:


bracket (mallocBytes size) free usará C malloc y free, mientras que allocaBytes size usará la memoria administrada por la recolección de basura de GHC. Eso en sí ya es una gran diferencia, ya que el Ptr de allocaBytes podría estar rodeado de memoria no utilizada (pero asignada):

import Control.Exception
import Control.Monad (forM_)
import Foreign.Marshal.Alloc
import Foreign.Ptr
import Foreign.Storable

-- Write a value at an invalid pointer location
hammer :: Ptr Int -> IO ()
hammer ptr = pokeElemOff ptr (-1) 0 >> putStrLn "hammered"

main :: IO ()
main = do
  putStrLn "Hammer time! Alloca!"
  forM_ [1..10] $ \n ->
    print n >> allocaBytes 10 hammer

  putStrLn "Hammer time! Bracket"
  forM_ [1..10] $ \n ->
    print n >> bracket (mallocBytes 10) free hammer

Resultado:

Hammer time! Alloca!
1
hammered
2
hammered
3
hammered
4
hammered
5
hammered
6
hammered
7
hammered
8
hammered
9
hammered
10
hammered
Hammer time! Bracket
1
hammered
<program crashes>

Como puedes ver, aunque hemos usado arr[-1] = 0, allocaBytes felizmente ignoró ese error. Sin embargo, free (a menudo) estallará en su cara si escribe en posición -1. También explotará en su cara si ha habido una corrupción de memoria en otra región de memoria asignada *.

También con allocaBytes, es probable que el puntero apunte a algún lugar de la memoria ya asignada, no al inicio de una, p. ej.

nursery = malloc(NURSERY_SIZE);

// ...

pointer_for_user = nursery + 180;

// pointer_for_user[-1] = 0 is not as 
// much as a problem, since it doesn't yield undefined behaviour

Qué significa eso? Bien, allocaBytes es menos probable que explote en su cara, pero a costa de no notar si su variante de código C daría lugar a daños en la memoria. Peor aún, tan pronto como escriba fuera de los límites devueltos por allocaBytes, tu posiblemente corruptora otra Haskell valores silenciosamente

Sin embargo, estamos hablando de un comportamiento indefinido. El código anterior puede o no bloquearse en su sistema. También puede bloquearse en el allocaBytes parte.

Si yo fuera tú lo haría rastrear el malloc y free llamadas.


* Una vez tuve un error de "doble uso de gratis" en el medio de mi programa. Depurado todo, reescribió la mayor parte de la rutina "mala". Lamentablemente, el error desapareció en las compilaciones de depuración, pero se repitió en versiones de lanzamiento. Resultó que en las primeras diez líneas de main, Accidentalmente escribí a b[i - 1] con i = 0.


12
2017-12-31 20:18



Pude duplicar el problema, y ​​puedo confirmar que hay un considerable desbordamiento de búfer en marcha. Si usa el siguiente asignador (disculpe el código rápido y sucio) que agrega el valor de una página de 0xa5s después del búfer y lo vuelca si se modifica, puede ver un desbordamiento de varios cientos de bytes en varias pruebas:

withBuffer :: Int -> (Ptr a -> IO b) -> IO b
withBuffer n = bracket begin end
  where begin = do
          a <- mallocBytes (n + 4096)
          mapM_ (\i -> pokeByteOff (a `plusPtr` n) i (0xa5 :: Word8)) [0..4095]
          return a
        end = \a -> do
          page <- mapM (\i -> peekByteOff (a `plusPtr` n) i) [0..4095]
          when (any (/= (0xa5 :: Word8)) page) $ do
            putStrLn $ unlines $ map (hexline page) [0,16..4095]
            error "corruption detected"
          free a
        hexline bytes off = unwords . map hex . take 16 . drop off $ bytes
        hex = printf "%02x"

5
2017-12-31 21:01