Nim:参数和可变性的地址

blu*_*e10 9 pointers nim-lang

我正在努力决定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
Run Code Online (Sandbox Code Playgroud)

一方面这是有道理的,因为一个参数看起来就像一个let绑定,它也"没有地址".另一方面,我对手册中的这句话感到困惑:

var参数对于有效的参数传递永远不是必需的.

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

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

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

我最后的参数地址之谜:我试图通过将类型更改为显式来明确复制的必要性Buffer {.bycopy.} = object.在这种情况下,副本已经在呼叫时发生,我希望现在可以访问该地址.为什么在这种情况下拒绝访问?

Rei*_*nds 7

您可以buf.data通过使用shallowCopy 来避免深层复制,例如:

var tmp: seq[float]
shallowCopy tmp, buf.data
Run Code Online (Sandbox Code Playgroud)

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

您不能获取buf不在其后面的地址或其任何部分,ref或者ptr因为将值作为非var参数传递是被调用者不修改参数的承诺.该shallowCopy内置的是一个不安全的功能,规避这样的保证(我记得这表明shallowCopy应该适当地改名unsafeShallowCopy以反映并有一个新的shallowCopy,其中第二个参数是一个var参数也可以).

  • `shallowCopy`相当于C中的普通赋值; 而Nim中的`=`为字符串,seqs和非ref,包含它们的非ptr对象做了一个(深层)结构副本(有点类似于`std :: string`和`std :: vector`的赋值方式在C++). (2认同)

zah*_*zah 6

让我们首先澄清以下内容:

var参数对于有效的参数传递永远不是必需的.

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

当您需要将序列传递给外部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)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

然后,可以将原始缓冲区传递给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)
Run Code Online (Sandbox Code Playgroud)