Trasmettere un riferimento di funzione che produce un puntatore non valido?


9

Sto rintracciando un errore nel codice di terze parti e l'ho ridotto a qualcosa lungo le righe di.

use libc::c_void;

pub unsafe fn foo() {}

fn main() {
    let ptr = &foo as *const _ as *const c_void;
    println!("{:x}", ptr as usize);
}

Funzionato su stabile 1.38.0 questo stampa il puntatore a funzione, ma beta (1.39.0-beta.6) e ritorno notturno '1'. ( Parco giochi )

Da cosa viene _inferito e perché il comportamento è cambiato?

Presumo che il modo corretto di lanciare questo sarebbe semplicemente foo as *const c_void, ma questo non è il mio codice.


Non posso rispondere al "perché è cambiato", ma concordo con te sul fatto che il codice non è corretto all'inizio. fooè già un puntatore a funzione, quindi non dovresti prenderne un indirizzo. Ciò crea un doppio riferimento, apparentemente a un tipo di dimensione zero (quindi il valore magico 1).
Shepmaster,

Questo non risponde esattamente alla tua domanda, ma probabilmente vorrai:let ptr = foo as *const fn() as *const c_void;
Peter Hall,

Risposte:


3

Questa risposta si basa sulle risposte sulla segnalazione di bug motivate da questa domanda .

Ogni funzione in Rust ha il suo tipo di elemento funzione individuale , che è distinto dal tipo di elemento funzione di ogni altra funzione. Per questo motivo, un'istanza del tipo di elemento della funzione non deve archiviare alcuna informazione: la funzione a cui punta è chiara dal suo tipo. Quindi la variabile x in

let x = foo;

è una variabile di dimensione 0.

I tipi di elementi funzione implicitamente costringono a funzionare tipi di puntatore dove necessario. La variabile

let x: fn() = foo;

è un puntatore generico a qualsiasi funzione con firma fn()e quindi deve memorizzare un puntatore alla funzione a cui effettivamente fa riferimento, quindi la dimensione di xè la dimensione di un puntatore.

Se prendi l'indirizzo di una funzione, &foostai effettivamente prendendo l'indirizzo di un valore temporaneo di dimensione zero. Prima di eseguire il commit nel rustrepository , i provvisori di dimensioni zero utilizzate per creare un'allocazione nello stack e &foorestituivano l'indirizzo di tale allocazione. Da questo commit, i tipi di dimensione zero non creano più allocazioni e utilizzano invece l'indirizzo magico 1. Questo spiega la differenza tra le diverse versioni di Rust.


Questo ha senso, ma non sono convinto che sia generalmente un comportamento desiderato perché è basato su un presupposto precario. Nel codice Rust sicuro, non vi è alcun motivo per distinguere i puntatori da un valore di uno ZST, poiché esiste un solo valore che è noto al momento della compilazione. Questo si interrompe quando è necessario utilizzare un valore ZST al di fuori del sistema di tipo Rust, come qui. Probabilmente influenza solo i fntipi di oggetti e le chiusure non bloccanti e per quelli c'è una soluzione alternativa, come nella mia risposta, ma è ancora abbastanza un colpo di pistola!
Peter Hall,

Ok, non avevo letto le risposte più recenti sul problema di Github. Potrei ottenere un segfault con quel codice ma, se il codice potrebbe causare un segfault, immagino che il nuovo comportamento sia ok.
Peter Hall,

Bella risposta. @PeterHall Stavo pensando la stessa cosa, e non sono ancora al 100% sull'argomento, ma almeno per i provvisori e le altre variabili dello stack, non ci dovrebbero essere problemi a mettere tutti i valori di dimensione zero su 0x1 perché il compilatore non fa garantisce il layout dello stack e comunque non è possibile garantire l'unicità dei puntatori agli ZST. Ciò è diverso, per esempio, dal lancio di un *const i32a *const c_voidcui, a mio avviso, è comunque garantito preservare l'identità del puntatore.
Trento

2

Da cosa viene _inferito e perché il comportamento è cambiato?

Ogni volta che si esegue un cast puntatore non elaborato, è possibile modificare solo un'informazione (riferimento o puntatore non elaborato; mutabilità; tipo). Pertanto, se esegui questo cast:

let ptr = &foo as *const _

dato che sei passato da un riferimento a un puntatore non elaborato, il tipo inferito per _ deve essere invariato ed è quindi il tipo di foo, che è un tipo inesprimibile per la funzionefoo .

Invece di farlo, puoi eseguire il cast direttamente su un puntatore a funzione, che è espressibile nella sintassi Rust:

let ptr = foo as *const fn() as *const c_void;

Per quanto riguarda il motivo per cui è cambiato, è difficile da dire. Potrebbe essere un bug nella build notturna. Vale la pena segnalarlo - anche se non è un bug, probabilmente otterrai una buona spiegazione dal team del compilatore su ciò che sta realmente accadendo!



@MaciejGoszczycki Grazie per aver segnalato! Le risposte in realtà hanno chiarito le cose per me - posterò una risposta in base alle risposte lì.
Sven Marnach,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.