“bracket(mallocBytes n)free”和“allocaBytes”之间有什么区别?

时间:2016-12-31 18:29:07

标签: haskell ffi

如果您想要背景,请参阅here。简而言之,问题是:“来自Foreign.Marshall.Allocbracket (mallocBytes n) freeallocaBytes之间的实际差异是什么。”

通常在C中,alloca在堆栈上分配,malloc在堆上分配。我不确定在Haskell中它发生了什么,但我不希望上述方程式与速度之外的区别。如果您单击了后台链接,您知道编译代码bracket (mallocBytes n) free导致“双重免费或损坏”,而allocaBytes工作正常(在GHCi中根本无法看到问题,一切正常两种情况都很好。)

到现在为止,我花了两天时间进行痛苦的调试,我非常确信bracket (mallocBytes n) free在某种程度上是不稳定的,其余的代码是可靠的。我想知道与bracket (mallocBytes n) free有什么关系。

2 个答案:

答案 0 :(得分:12)

bracket (mallocBytes size) free将使用C mallocfree,而allocaBytes size将使用由GHC垃圾回收管理的内存。这本身就是一个巨大的差异,因为Ptr的{​​{1}}可能被未使用(但已分配)的内存所包围:

allocaBytes

结果:

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

正如您所看到的,虽然我们已经使用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> ,但arr[-1] = 0很高兴地忽略了该错误。但是,如果您写到职位allocaBytesfree会(经常)在您的脸上爆炸。如果另一个分配的内存区域存在内存损坏,它也会在你的脸上爆炸*。

此外,对于-1,指针很可能指向已经分配的内存,而不是指向一个内存的开头,例如。

allocaBytes

这是什么意思?好吧,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 不太可能在你的脸上爆炸,但是你不会注意到你的C代码变体是否会导致内存损坏。更糟糕的是,只要你在allocaBytes返回的边界之外写字,你可能会破坏其他 Haskell 值。

但是,我们在这里谈论未定义的行为。上面的代码可能会或可能不会在您的系统上崩溃。它也可能在allocaBytes部分崩溃。

如果我是你,我会trace the malloc and free calls

*我曾经在程序中间出现“双重使用免费”错误。调试了所有内容,重写了大部分“坏”例程。不幸的是,错误在调试版本中消失了,但在发布版本中再次出现。事实证明,在allocaBytes的前十行中,我偶然使用main写信给b[i - 1]

答案 1 :(得分:5)

我能够复制问题,我可以确认存在大量缓冲区溢出。如果您使用以下分配器(请原谅快速和脏代码),它会在缓冲区之后添加页面的0xa5 s并将其转储(如果已修改),您可以在几个测试中看到超过几百个字节:

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"