2015-05-08 10 views
9

Sto cercando di prendere una decisione sulla politica di Nim dietro expression has no address. In particolare, ho una funzione C che prende un puntatore (+ lunghezza, ecc.) Di alcuni buffer di dati. So che questa funzione sarà non modificare i dati. Semplificata:Nim: Indirizzi di parametri e mutabilità

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 

Da un lato questo ha un senso, dal momento che un parametro sembra comportarsi esattamente come un let vincolante, che anche "non ha un indirizzo". D'altra parte, sono perplesso da questa affermazione nel manuale:

i parametri var non sono mai necessari per il passaggio efficiente dei parametri.

Per quanto posso vedere, l'unico modo per evitare di copiare i dati sono da uno:

  • passando il parametro come buf: var Buffer
  • passando un riferimento, cioè utilizzando un ref object.

In entrambi i casi questo suggerisce che la mia funzione modifica i dati. Inoltre, introduce la mutabilità sul sito del chiamante (ad esempio, gli utenti non possono più utilizzare i binding per i loro buffer). La domanda chiave per me è: poiché "so" che callToC è di sola lettura, posso convincere Nim a consentire sia l'immutabilità senza una copia? Vedo che questo è pericoloso, dal momento che devo sapere con certezza che la chiamata è immutabile. Quindi, ciò richiederebbe una sorta di meccanismo di "indirizzo non sicuro" che consentisse di forzare i puntatori a dati immutabili?

E il mio ultimo mistero degli indirizzi dei parametri: ho cercato di rendere esplicita la necessità della copia cambiando il tipo in Buffer {.bycopy.} = object. In questo caso la copia avviene già al momento della chiamata, e mi aspetto di avere accesso all'indirizzo ora. Perché l'accesso è negato anche in questo caso?

risposta

6

È possibile evitare la copia completa di buf.data utilizzando shallowCopy, ad esempio:

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

Il {.byCopy.} pragma influisce solo la convenzione di chiamata (vale a dire se un oggetto viene passato in pila o tramite un riferimento

.

Non è possibile prendere l'indirizzo di buf o qualsiasi parte di esso che non sia dietro a ref o ptr perché passare un valore come parametro non var è una promessa che il destinatario non modifica l'argomento. Il shallowCopy incorporato è uncaratteristica pericolosa che aggira tale garanzia (ricordo che suggerire che shallowCopy dovrebbe essere correttamente rinominato in unsafeShallowCopy per riflettere quello e avere un nuovo shallowCopy dove il secondo argomento è anche un parametro var). inizio

+0

Grazie mille, sembra che questo sia esattamente quello che stavo cercando. Quello che non capisco ancora del tutto: che cosa è esattamente "copiato" in 'shallowCopy 'qui? Se ti capisco correttamente (ad esempio, se la performance è simile al caso no-copy), è piuttosto una sorta di "non sicuriAlias"? – bluenote10

+2

'shallowCopy' è l'equivalente di un semplice compito in C; mentre '=' in Nim fa una copia strutturale (profonda) per stringhe, seq e oggetti non ref, non ptr che li contengono (in qualche modo simile a come l'assegnazione per il lavoro 'std :: string' e' std :: vector' in C++). –

+0

Ah, vedo, per un 'seq',' stringa', o qualsiasi altro tipo 'ref', semplicemente" copia "il puntatore. – bluenote10

6

Let chiarendo quanto segue:

parametri var non sono mai necessari per un efficiente passaggio di parametri.

Questo è generalmente vero, perché in Nim i valori complessi come oggetti, sequenze e stringhe saranno passati per indirizzo (a.k.a. per riferimento) a procs che accettano parametri di sola lettura.

Quando è necessario passare una sequenza a una funzione C/C++ esterna, le cose diventano un po 'più complicate. Il modo più comune per farlo è affidarsi al tipo openarray, che converte automaticamente la sequenza di una coppia di puntatore di dati e un intero di dimensioni:

# 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) 

non ci saranno copie negli generato C codice.

Ora, se la libreria C avvolta non accetta una coppia di argomenti data/dimensione come nell'esempio qui, suggerirei di creare un piccolo wrapper C attorno ad esso (è possibile creare un file di intestazione o semplicemente utilizzare il emettere pragma per creare le necessarie funzioni dell'adattatore o #define).

In alternativa, se si vuole veramente mettere le mani sporche, è possibile estrarre il buffer sottostante dalla sequenza con il seguente proc aiutante:

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

Poi, sarà possibile passare il buffer prima al C simili:

{.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) 
+0

Una cosa importante che ho imparato dalla tua risposta: quando scrivi un binding C per una funzione che accetta un array ** ** mutevole, non dimenticare mai il prefisso ** var **. Ho appena avuto un caso in cui l'autore di alcune associazioni C aveva dimenticato i vars. Il risultato: Nim mi consente di passare array di let let immutabili, ma sono effettivamente modificati. – bluenote10

+0

@zah, perché ti _helper proc_ emette invece di semplicemente restituire 'return ptr s [0]'? O non è lo stesso? – kerim

+0

@kerim, prendere l'indirizzo del primo elemento funzionerebbe solo se si ha a che fare con un tipo di sequenza 'var'. In caso contrario, l'operatore di indicizzazione restituisce valori, il cui indirizzo non può essere preso. – zah

1

Nim ora ha una unsafeAddr operatore, che consente di ottenere gli indirizzi anche per let attacchi e parametri, consentendo di evitare la soluzione shallowCopy. Ovviamente bisogna stare molto attenti che nulla muti i dati dietro il puntatore.