Nim:参数地址和可变性
Nim: Addresses of parameters and mutability
我正在对 expression has no address
背后的 Nim 政策下定决心。特别是,我有一个 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
绑定完全一样,后者也 "has no address"。另一方面,我对手册中的这个说法感到困惑:
var parameters are never necessary for efficient parameter passing.
据我所知,避免复制数据的唯一方法是:
- 将参数传递为
buf: var Buffer
- 传递引用,即使用
ref object
.
在这两种情况下,这表明我的函数修改了数据。此外,它在调用方站点上引入了可变性(即用户不能再对他们的缓冲区使用 let 绑定)。我的关键问题是:由于 "I know" 和 callToC
是只读的,我可以说服 Nim 在没有副本的情况下允许这两种不变性吗?我知道这很危险,因为我必须确定调用是不可变的。因此,这将需要某种 "unsafe address" 机制,允许强制指针指向不可变数据?
我最后的参数地址之谜:我试图通过将类型更改为 Buffer {.bycopy.} = object
来明确复制的必要性。在这种情况下,副本已经在调用时发生,我希望现在可以访问该地址。为什么在这种情况下访问也被拒绝?
可以通过shallowCopy避免buf.data
的深拷贝,例如:
var tmp: seq[float]
shallowCopy tmp, buf.data
{.byCopy.}
pragma 仅影响调用约定(即对象是在堆栈上传递还是通过引用传递。
您不能获取 buf
的地址或其不在 ref
或 ptr
后面的任何部分,因为将值作为非 var 参数传递是承诺被调用者不修改参数。 shallowCopy
内置是一个不安全的功能,它绕过了保证(我记得建议 shallowCopy
应该适当地重命名为 unsafeShallowCopy
以反映这一点并有一个新的 shallowCopy
其中第二个参数也是 var
参数。
让我们首先澄清以下几点:
var parameters are never necessary for efficient parameter passing.
这通常是正确的,因为在 Nim 中,对象、序列和字符串等复杂值将通过地址(a.k.a。通过引用)传递给接受只读参数的过程。
当您需要将序列传递给外部 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 库不接受此处示例中的一对 data/size 参数,我建议围绕它创建一个小型 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)
Nim 现在有一个 unsafeAddr
运算符,它甚至可以为 let
绑定和参数获取地址,从而避免 shallowCopy
变通方法。显然,必须非常小心,不要让指针后面的数据发生任何变化。
我正在对 expression has no address
背后的 Nim 政策下定决心。特别是,我有一个 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
绑定完全一样,后者也 "has no address"。另一方面,我对手册中的这个说法感到困惑:
var parameters are never necessary for efficient parameter passing.
据我所知,避免复制数据的唯一方法是:
- 将参数传递为
buf: var Buffer
- 传递引用,即使用
ref object
.
在这两种情况下,这表明我的函数修改了数据。此外,它在调用方站点上引入了可变性(即用户不能再对他们的缓冲区使用 let 绑定)。我的关键问题是:由于 "I know" 和 callToC
是只读的,我可以说服 Nim 在没有副本的情况下允许这两种不变性吗?我知道这很危险,因为我必须确定调用是不可变的。因此,这将需要某种 "unsafe address" 机制,允许强制指针指向不可变数据?
我最后的参数地址之谜:我试图通过将类型更改为 Buffer {.bycopy.} = object
来明确复制的必要性。在这种情况下,副本已经在调用时发生,我希望现在可以访问该地址。为什么在这种情况下访问也被拒绝?
可以通过shallowCopy避免buf.data
的深拷贝,例如:
var tmp: seq[float]
shallowCopy tmp, buf.data
{.byCopy.}
pragma 仅影响调用约定(即对象是在堆栈上传递还是通过引用传递。
您不能获取 buf
的地址或其不在 ref
或 ptr
后面的任何部分,因为将值作为非 var 参数传递是承诺被调用者不修改参数。 shallowCopy
内置是一个不安全的功能,它绕过了保证(我记得建议 shallowCopy
应该适当地重命名为 unsafeShallowCopy
以反映这一点并有一个新的 shallowCopy
其中第二个参数也是 var
参数。
让我们首先澄清以下几点:
var parameters are never necessary for efficient parameter passing.
这通常是正确的,因为在 Nim 中,对象、序列和字符串等复杂值将通过地址(a.k.a。通过引用)传递给接受只读参数的过程。
当您需要将序列传递给外部 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 库不接受此处示例中的一对 data/size 参数,我建议围绕它创建一个小型 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)
Nim 现在有一个 unsafeAddr
运算符,它甚至可以为 let
绑定和参数获取地址,从而避免 shallowCopy
变通方法。显然,必须非常小心,不要让指针后面的数据发生任何变化。