UB 是否由于 allocaArray 自动清理?

UB due to allocaArray automatic cleanup or not?

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

-- 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 数组的辅助函数 API,例如 f 可能是 vkEnumeratePhysicalDevices

当我检查我的代码时,我注意到它 returns resArray(从 allocaArray 的描述来看,它似乎只在内部 lambda 中有效)到它的调用者。在 C 中,这样的代码将是未定义的行为。我的直觉在这里是正确的还是还有其他事情发生?毕竟我还没有注意到任何崩溃:)

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

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

正确的方法是:

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