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
только для чтения, могу ли я убедить Нима разрешить обе неизменности без копии? Я вижу, что это опасно, так как я должен точно знать, что вызов неизменен. Таким образом, для этого потребуется какой-то механизм "небезопасного адреса", позволяющий принудительно указывать на неизменяемые данные?
И моя последняя загадка адресов параметров: я попытался сделать копию явной, изменив тип на Buffer {.bycopy.} = object
, В этом случае копирование уже происходит во время вызова, и я ожидаю, что теперь у меня будет доступ к адресу. Почему в этом случае доступ запрещен?
3 ответа
Вы можете избежать глубокой копии buf.data
используя shallowCopy, например:
var tmp: seq[float]
shallowCopy tmp, buf.data
{.byCopy.}
Прагма влияет только на соглашение о вызовах (т.е. передается ли объект в стеке или по ссылке.
Вы не можете взять адрес buf
или любая его часть, которая не находится за ref
или же ptr
потому что передача значения в качестве параметра без переменной является обещанием, что вызываемый объект не изменяет аргумент. shallowCopy
Встроенная функция - небезопасная функция, которая обходит эту гарантию (я помню, что shallowCopy
следует правильно переименовать в unsafeShallowCopy
чтобы отразить это и иметь новый shallowCopy
где второй аргумент является var
параметр тоже).
Давайте начнем с уточнения следующего:
Параметры var никогда не нужны для эффективной передачи параметров.
Как правило, это так, потому что в Nim сложные значения, такие как объекты, последовательности и строки, будут передаваться по адресу (он же по ссылке) процессорам, принимающим параметры только для чтения.
Когда вам нужно передать последовательность во внешнюю функцию 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 для создания необходимые функции адаптера или #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)
Ним теперь имеет unsafeAddr
оператор, который позволяет получать адреса даже для let
привязки и параметры, позволяющие избежать shallowCopy
обходной путь. Очевидно, что нужно быть очень осторожным, чтобы ничто не изменило данные за указателем.