在我的代码中,我有这个函数,似乎工作得很好。
-- type declaration just for reference, i don't have it in my actual code
retrieveVulkanArray :: Storable a => (Ptr Word32 -> Ptr a -> IO b) -> IO (Ptr a, Int)
retrieveVulkanArray' f =
alloca $ \arrCount -> do
f arrCount vkNullPtr
arrCount' <- fromIntegral <$> peek arrCount
allocaArray arrCount' $ \resArray -> do
f arrCount resArray
pure (resArray, arrCount')
(为方便起见,这是一个从Vulkan API中获取FFI数组的辅助函数,例如f可能是 vkEnumeratePhysicalDevices(物理设备))
当我回顾我的代码时,我注意到它返回resArray(从 allocaArray 的描述来看,似乎只有在内部 lambda 中才有效)给它的调用者。在C语言中,这样的代码是未定义的行为。我的直觉是正确的吗,还是有更多的事情发生?毕竟我还没有注意到任何崩溃:)
能用当然不能证明它是正确的,事实上这个函数确实是非常错误的。
alloca
以及 allocaArray
,将分配一个Haskell MutableByteArray#
将其转换为一个指针。对该指针进行操作,然后用一个特殊的 touch#
函数。问题是,一旦你失去了对实际的 MutableByteArray#
这就是当你退出 alloca
,GC将清理它和 Ptr a
指向该数组的指针将不再有效。所以,如果你继续向那个指针读或写东西 Ptr a
退回后 retrieveVulkanArray
你正在向内存中读入可以被其他东西使用的内容,并且现在有发生segfault的真正危险,以及随之而来的各种其他安全漏洞。
正确的方式应该是。
retrieveVulkanArray
:: Storable a => (Ptr Word32 -> Ptr a -> IO b) -> IO (ForeignPtr a, Int)
retrieveVulkanArray' f =
alloca $ \arrCount -> do
f arrCount vkNullPtr
arrCount' <- fromIntegral <$> peek arrCount
resArray <- mallocForeignPtrArray arrCount'
_ <- withForeignPtr resArray (f arrCount)
pure (resArray, arrCount')
ForeignPtr a
确保你可以在原始的 Ptr a
需要时,不用担心它指向的内存被释放,直到 ForeignPtr
已不再使用。