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 обходной путь. Очевидно, что нужно быть очень осторожным, чтобы ничто не изменило данные за указателем.

Другие вопросы по тегам