如何通过Haskell FFI传递不规则数组(Ragged Arrays)
Haskell调用C二维数组函数的内存安全实现
要实现内存自动清理的二维数组传递,核心思路是用withArray处理每个内层数组(它会自动在作用域结束后释放内存),再通过mapM批量处理所有内层列表,最后把收集到的内层指针写入外层指针数组。
完整实现代码如下:
import Foreign.C.Types (CInt, CDouble) import Foreign.Ptr (Ptr) import Foreign.Marshal.Array (allocaArray, pokeArray, withArray) import System.IO.Unsafe (unsafePerformIO) foreign import ccall "foo.h foo" c_foo :: CInt -> Ptr CInt -> Ptr (Ptr CDouble) -> CDouble haskFoo :: Int -> [Int] -> [[Double]] -> Double haskFoo n lengths dataLists = unsafePerformIO $ allocaArray n $ \lengths_ptr -> do -- 写入每行长度的C数组 pokeArray lengths_ptr (map fromIntegral lengths) allocaArray n $ \data_outer_ptrs -> do -- 为每个内层Haskell列表分配C数组,获取指针(内存自动管理) innerPtrs <- mapM (\row -> withArray (map realToFrac row) return) dataLists -- 将内层指针写入外层指针数组 pokeArray data_outer_ptrs innerPtrs -- 调用C函数,此时所有内层数组内存都有效 cResult <- c_foo (fromIntegral n) lengths_ptr data_outer_ptrs return $ realToFrac cResult
关键细节说明
withArray:每个内层列表通过withArray转成C数组,它的内存会在当前作用域(直到c_foo调用完成)结束后自动释放,无需手动free,彻底避免内存泄漏。mapM:遍历所有内层列表,批量生成对应的C数组指针,解决了运行时数量的嵌套lambda问题。- 类型转换:
map realToFrac row把Haskell的Double转成C兼容的CDouble,map fromIntegral lengths把Int转成CInt,保证和C函数的参数类型完全匹配。
注意事项
- 必须保证输入的
lengths列表长度、dataLists的外层长度都等于n,否则会出现数组越界。建议在函数开头添加断言检查,比如:import Control.Exception (assert) -- ... haskFoo n lengths dataLists = assert (length lengths == n && length dataLists == n) $ unsafePerformIO $ unsafePerformIO的使用是安全的,只要haskFoo的输入是纯值,且C函数foo没有副作用(比如不修改输入数组、不产生外部状态变化),对外可以保持纯函数的接口。
内容的提问来源于stack exchange,提问作者bosmacs
相关产品推荐
相关产品推荐

