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

Mar*_*ark 8 haskell ffi

如果你想要背景,请看这里.简而言之,问题是:" 从bracket (mallocBytes n) free和之间的实际差异是什么".allocaBytesForeign.Marshall.Alloc

通常在C中,alloca在堆栈malloc上分配并在堆上分配.我不确定在Haskell中它发生了什么,但我不希望上述方程式与速度之外的区别.如果你点击后台链接,你知道编译代码bracket (mallocBytes n) free导致"双重免费或腐败",同时allocaBytes工作正常(在GHCi中根本无法看到问题,在这两种情况下一切正常).

到现在为止,我已经花了两天时间进行痛苦的调试,而且我非常有信心bracket (mallocBytes n) free在某种程度上不稳定,其余的代码都是可靠的.我想知道这是什么交易bracket (mallocBytes n) free.

Zet*_*eta 12

bracket (mallocBytes size) free将用C的mallocfree,而allocaBytes size将使用由真实GHCs垃圾回收托管内存.这本身就是一个巨大的差异已经,因为PtrallocaBytes可能是由未使用(但分配的)内存包围:

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
Run Code Online (Sandbox Code Playgroud)

结果:

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>
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,尽管我们已经使用arr[-1] = 0,allocaBytes愉快地忽略了错误.但是,free如果你写到位置,会(经常)在你脸上爆炸-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
Run Code Online (Sandbox Code Playgroud)

那是什么意思?好吧,allocaBytes不太可能在你的脸上爆炸,但是如果你的C代码变体会导致内存损坏,你不会注意到这个代价.更糟糕的是,只要你在返回的边界之外写字allocaBytes,你可能会破坏其他Haskell的价值.

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

如果我是你,我会跟踪mallocfree打电话.


*我曾经在程序中间出现"双重使用免费"错误.调试了所有内容,重写了大部分"坏"例程.不幸的是,错误在调试版本中消失了,但在发布版本中再次出现.原来,在前十行中main,我不小心写信给b[i - 1]i = 0.


K. *_*uhr 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"
Run Code Online (Sandbox Code Playgroud)