You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 22:55:03