10

我正在努力对 Nim 背后的政策下定决心expression has no address。特别是,我有一个 C 函数,它接受一些数据缓冲区的指针(+ 长度等)。我知道这个函数不会修改数据。简化:

type
  Buffer = object
    data: seq[float]

proc wrapperForCCall(buf: Buffer) =
  # accessing either buf.addr nor buf.data.addr produces
  # Error: expression has no address
  # workaround:
  var tmp = buf.data          # costly copy
  callToC(tmp.len, tmp.addr)  # now it works

一方面这是有道理的,因为参数的行为似乎与let绑定完全一样,它也“没有地址”。另一方面,我对手册中的这句话感到困惑:

var 参数对于有效的参数传递从来不是必需的。

据我所知,避免复制数据的唯一方法是:

  • 将参数传递为buf: var Buffer
  • 传递引用,即使用ref object.

在这两种情况下,这表明我的函数修改了数据。此外,它在调用者站点上引入了可变性(即用户不能再对他们的缓冲区使用 let 绑定)。对我来说关键问题是:既然“我知道”callToC是只读的,我可以说服 Nim 在没有副本的情况下允许这两个不变性吗?我看到这很危险,因为我必须确定调用是不可变的。因此,这将需要某种“不安全地址”机制,允许强制指向不可变数据的指针?

还有我对参数地址的最后一个谜团:我试图通过将类型更改为Buffer {.bycopy.} = object. 在这种情况下,副本已经在调用时发生,我希望现在可以访问该地址。为什么在这种情况下也拒绝访问?

4

3 回答 3

7

buf.data您可以通过使用 shallowCopy来避免深拷贝,例如:

var tmp: seq[float]
shallowCopy tmp, buf.data

pragma 只影响调用约定(即对象是在{.byCopy.}堆栈上传递还是通过引用传递。

您不能获取不在 a 或后面的地址或其buf任何部分,因为将值作为非 var 参数传递是被调用者不会修改参数的承诺。内置函数是一个不安全的特性,它绕过了这个保证(我记得建议应该正确地重命名以反映这一点,并在第二个参数也是参数的地方有一个新的)。refptrshallowCopyshallowCopyunsafeShallowCopyshallowCopyvar

于 2015-05-08T21:24:33.940 回答
6

让我们从澄清以下内容开始:

var 参数对于有效的参数传递从来不是必需的。

这通常是正确的,因为在 Nim 中,对象、序列和字符串等复杂值将通过地址(也称为通过引用)传递给接受只读参数的 procs。

当您需要将序列传递给外部 C/C++ 函数时,事情会变得有点复杂。最常见的方法是依赖openarray类型,它会自动将序列转换为一对数据指针和一个大小整数:

# Let's say we have the following C function:

{.emit: """

#include <stdio.h>

void c_call_with_size(double *data, size_t len)
{
  printf("first value: %f; size: %d \n" , data[0], len);
}

""".}

# We can import it like this:

proc c_call(data: openarray[float]) {.importc: "c_call_with_size", nodecl.}

# The usage is straight-forward:

type Buffer = object
  data: seq[float]

var b = Buffer(data: @[1.0, 2.0])

c_call(b.d)

生成的 C 代码中不会有任何副本。

现在,如果包装的 C 库不接受此处示例中的一对数据/大小参数,我建议在其周围创建一个小型 C 包装器(您可以创建一个头文件或只使用 emit pragma 创建必要的适配器函数或#defines)。

或者,如果你真的想弄脏你的手,你可以使用以下帮助程序从序列中提取底层缓冲区:

proc rawBuffer[T](s: seq[T]): ptr T =
  {.emit: "result = `s`->data;".}

然后,可以像这样将原始缓冲区传递给 C:

{.emit: """

#include <stdio.h>

void c_call(double *data)
{
  printf("first value: %f \n", data[0]);
}

""".}

proc c_call(data: ptr float) {.importc: "c_call", nodecl.}

var b = Buffer(data: @[1.0, 2.0])
c_call(b.data.rawBuffer)
于 2015-05-10T09:36:02.400 回答
3

Nim 现在有一个unsafeAddr操作符,它甚至可以获取let绑定和参数的地址,从而避免了shallowCopy变通方法。显然,必须非常小心,不要改变指针后面的数据。

于 2017-07-17T17:54:01.553 回答