Przesyłanie odwołania do funkcji, które generuje nieprawidłowy wskaźnik?

9

Śledzę błąd w kodzie strony trzeciej i zawęziłem go do czegoś podobnego do.

use libc::c_void;

pub unsafe fn foo() {}

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

Działa na stabilnej wersji 1.38.0, wypisuje wskaźnik funkcji, ale beta (1.39.0-beta.6) i nocne zwrócenie „1”. ( Plac zabaw )

Jakie są _wnioski i dlaczego zachowanie się zmieniło?

Zakładam, że właściwy sposób na przesłanie tego byłoby po prostu foo as *const c_void, ale to nie jest mój kod.

Maciej Goszczycki
źródło
Nie mogę odpowiedzieć na pytanie „dlaczego to się zmieniło”, ale zgadzam się z Tobą, że kod jest nieprawidłowy na początku. foojest już wskaźnikiem funkcji, więc nie powinieneś do niego adresować. To tworzy podwójne odniesienie, pozornie do typu zerowego (a więc magicznej wartości 1).
Shepmaster
To nie do końca odpowiada na twoje pytanie, ale prawdopodobnie chcesz:let ptr = foo as *const fn() as *const c_void;
Peter Hall

Odpowiedzi:

3

Ta odpowiedź jest oparta na odpowiedziach na raport o błędzie motywowanych tym pytaniem .

Każda funkcja w Rust ma swój indywidualny typ elementu funkcji , który różni się od typu elementu funkcji każdej innej funkcji. Z tego powodu instancja typu elementu funkcji w ogóle nie musi przechowywać żadnych informacji - funkcja, na którą wskazuje, wynika z jego typu. Zatem zmienna x in

let x = foo;

jest zmienną o rozmiarze 0.

Typy elementów funkcji domyślnie wymuszają, w razie potrzeby, typy wskaźników funkcji . Zmienna

let x: fn() = foo;

jest ogólnym wskaźnikiem do dowolnej funkcji z podpisem fn(), a zatem musi przechowywać wskaźnik do funkcji, na którą faktycznie wskazuje, więc rozmiar xto rozmiar wskaźnika.

Jeśli weźmiesz adres funkcji, &foofaktycznie bierzesz adres tymczasowej wartości zerowej wielkości. Przed tym zatwierdzeniem do rustrepozytorium tymczasowe o zerowej wielkości zostały użyte do utworzenia przydziału na stosie i &foozwróciły adres tego przydziału. Ponieważ to zatwierdzenie, typy zerowe nie tworzą już przydziałów, zamiast tego używają magicznego adresu 1. To wyjaśnia różnicę między różnymi wersjami Rdzy.

Sven Marnach
źródło
Ma to sens, ale nie jestem przekonany, że jest to ogólnie pożądane zachowanie, ponieważ opiera się na niepewnym założeniu. W ramach bezpiecznego kodu Rust nie ma powodu, aby rozróżniać wskaźniki od wartości ZST - ponieważ istnieje tylko jedna możliwa wartość znana w czasie kompilacji. Rozkłada się, gdy trzeba użyć wartości ZST poza systemem typu Rust, na przykład tutaj. Prawdopodobnie wpływa to tylko na fntypy przedmiotów i zamknięcia, których nie można przechwycić, a dla tych istnieje obejście, jak w mojej odpowiedzi, ale wciąż jest to strzał w dziesiątkę!
Peter Hall
Ok, nie przeczytałem nowszych odpowiedzi na temat Github. Mógłbym dostać awarię z tym kodem, ale jeśli kod może spowodować awarię, to myślę, że nowe zachowanie jest w porządku.
Peter Hall
Świetna odpowiedź. @PeterHall Myślałem o tym samym i nadal nie jestem w 100% na ten temat, ale przynajmniej w przypadku tymczasowych i innych zmiennych stosu nie powinno być problemu z ustawieniem wszystkich wartości zerowych na 0x1, ponieważ kompilator nie gwarantuje układ stosu i nie można zagwarantować niepowtarzalności wskaźników ZST. Różni się to od, powiedzmy, rzucenia, *const i32do *const c_voidktórego, o ile rozumiem, nadal gwarantuje zachowanie tożsamości wskaźnika.
trentcl
2

Jakie są _wnioski i dlaczego zachowanie się zmieniło?

Za każdym razem, gdy wykonujesz rzut surowym wskaźnikiem, możesz zmienić tylko jedną informację (wskaźnik lub wskaźnik surowy; zmienność; typ). Dlatego jeśli wykonasz tę obsadę:

let ptr = &foo as *const _

ponieważ zmieniłeś odniesienie na surowy wskaźnik, typ wywnioskowany dla _ musi być niezmieniony i dlatego jest typem foo, który jest jakimś niewyrażalnym typem dla funkcji foo.

Zamiast tego możesz bezpośrednio rzutować na wskaźnik funkcji, który wyraża się w składni Rust:

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

Trudno powiedzieć, dlaczego tak się zmieniło. Może to być błąd w kompilacji nocnej. Warto to zgłosić - nawet jeśli nie jest to błąd, zespół kompilatora prawdopodobnie wyjaśni ci, co się naprawdę dzieje!

Peter Hall
źródło
1
Dzięki, zgłosiłem to github.com/rust-lang/rust/issues/65499
Maciej Goszczycki
@MaciejGoszczycki Dziękujemy za zgłoszenie! Odpowiedzi faktycznie wyjaśniły mi sprawę - opublikuję odpowiedź na podstawie tamtych odpowiedzi.
Sven Marnach