"bracket (mallocBytes n) free" 和 "allocaBytes" 有什么区别?
What is the difference between "bracket (mallocBytes n) free" and "allocaBytes"?
如果需要背景,请参阅here. In short, the question is: “What's actual difference between bracket (mallocBytes n) free
and allocaBytes
from Foreign.Marshall.Alloc
”。
通常在 C 中,alloca
在堆栈上分配,malloc
在堆上分配。我不确定 Haskell 中发生了什么,但除了速度之外,我不希望上述方程式之间存在差异。但是,如果您单击背景 link,您就会知道编译后的代码 bracket (mallocBytes n) free
会导致“双重释放或损坏”,而 allocaBytes
工作正常(在 GHCi 中该问题不可见所有,在这两种情况下一切正常。
到目前为止,我已经花了两天时间进行痛苦的调试,而且我非常有信心 bracket (mallocBytes n) free
不知何故不稳定,其余代码是可靠的。我想知道 bracket (mallocBytes n) free
.
是怎么回事
bracket (mallocBytes size) free
将使用 C 的 malloc
和 free
,而 allocaBytes size
将使用由 GHC 垃圾回收管理的内存。这本身已经是一个巨大的差异,因为 allocaBytes
的 Ptr
可能被未使用(但已分配)的内存包围:
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
,但 allocaBytes
愉快地忽略了该错误。但是,如果您写到位置 -1
,free
会(经常)当着您的面爆炸。如果另一个分配的内存区域发生内存损坏,它也会当面爆炸*。
此外,对于 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
这是什么意思?好吧,allocaBytes
不太可能在您面前爆炸,但代价是您不会注意到您的 C 代码变体是否会导致内存损坏。更糟糕的是,一旦您在 allocaBytes
返回的范围之外写入,您可能会悄悄破坏其他 Haskell 值。
但是,我们在这里讨论的是未定义的行为。上面的代码可能会或可能不会在您的系统上崩溃。它也可能在 allocaBytes
部分崩溃。
如果我是你,我会trace the malloc
and free
calls。
* 我曾经在我的程序中间遇到一个 "double use of free" 错误。调试了所有内容,重写了大部分 "bad" 例程。不幸的是,错误在调试版本中消失了,但在发布版本中再次出现。原来在main
的前十行,不小心把b[i - 1]
写成了i = 0
。
我能够重现该问题,并且可以确认存在大量缓冲区溢出。如果您使用以下分配器(请原谅快速和肮脏的代码),它在缓冲区后添加一个页面的价值 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"
如果需要背景,请参阅here. In short, the question is: “What's actual difference between bracket (mallocBytes n) free
and allocaBytes
from Foreign.Marshall.Alloc
”。
通常在 C 中,alloca
在堆栈上分配,malloc
在堆上分配。我不确定 Haskell 中发生了什么,但除了速度之外,我不希望上述方程式之间存在差异。但是,如果您单击背景 link,您就会知道编译后的代码 bracket (mallocBytes n) free
会导致“双重释放或损坏”,而 allocaBytes
工作正常(在 GHCi 中该问题不可见所有,在这两种情况下一切正常。
到目前为止,我已经花了两天时间进行痛苦的调试,而且我非常有信心 bracket (mallocBytes n) free
不知何故不稳定,其余代码是可靠的。我想知道 bracket (mallocBytes n) free
.
bracket (mallocBytes size) free
将使用 C 的 malloc
和 free
,而 allocaBytes size
将使用由 GHC 垃圾回收管理的内存。这本身已经是一个巨大的差异,因为 allocaBytes
的 Ptr
可能被未使用(但已分配)的内存包围:
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
,但 allocaBytes
愉快地忽略了该错误。但是,如果您写到位置 -1
,free
会(经常)当着您的面爆炸。如果另一个分配的内存区域发生内存损坏,它也会当面爆炸*。
此外,对于 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
这是什么意思?好吧,allocaBytes
不太可能在您面前爆炸,但代价是您不会注意到您的 C 代码变体是否会导致内存损坏。更糟糕的是,一旦您在 allocaBytes
返回的范围之外写入,您可能会悄悄破坏其他 Haskell 值。
但是,我们在这里讨论的是未定义的行为。上面的代码可能会或可能不会在您的系统上崩溃。它也可能在 allocaBytes
部分崩溃。
如果我是你,我会trace the malloc
and free
calls。
* 我曾经在我的程序中间遇到一个 "double use of free" 错误。调试了所有内容,重写了大部分 "bad" 例程。不幸的是,错误在调试版本中消失了,但在发布版本中再次出现。原来在main
的前十行,不小心把b[i - 1]
写成了i = 0
。
我能够重现该问题,并且可以确认存在大量缓冲区溢出。如果您使用以下分配器(请原谅快速和肮脏的代码),它在缓冲区后添加一个页面的价值 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"