1

我的代码中有这个功能似乎工作得很好:

-- 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 中,这样的代码将是未定义的行为。我的直觉在这里是正确的还是有更多的事情发生?毕竟我还没有注意到任何崩溃:)

4

1 回答 1

2

它起作用的事实当然不能证明它是正确的,实际上这个功能确实是非常错误的。

alloca, 以及allocaArray, 将分配一个 HaskellMutableByteArray#将其转换为指针。touch#对该指针进行操作,然后使用特殊函数确保该数组仍然存在。问题是,一旦你失去对实际的引用MutableByteArray#,这就是你退出时发生的事情alloca,GC 将清理它并且Ptr a指向该数组的那个将不再有效。Ptr a因此,如果您在从该指针返回后继续读取或写入该指针,则retrieveVulkanArray您正在读取/写入可由其他东西使用的内存,并且现在真正面临段错误和随之而来的各种其他安全漏洞的危险。

正确的方法是:

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不再使用。

于 2020-05-09T16:14:16.103 回答