Casting referensi fungsi yang menghasilkan pointer yang tidak valid?

9

Saya melacak kesalahan dalam kode pihak ketiga dan saya mempersempitnya menjadi sesuatu di sepanjang baris.

use libc::c_void;

pub unsafe fn foo() {}

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

Berjalan di stabil 1.38.0 ini mencetak penunjuk fungsi, tetapi beta (1.39.0-beta.6) dan kembali setiap malam '1'. ( Taman bermain )

Apa yang _disimpulkan dan mengapa perilaku berubah?

Saya berasumsi cara yang tepat untuk melakukan ini foo as *const c_void, tapi ini bukan kode saya.

Maciej Goszczycki
sumber
Saya tidak dapat menjawab "mengapa itu berubah", tetapi saya setuju dengan Anda bahwa kode tersebut tidak benar untuk memulai. foosudah menjadi penunjuk fungsi, jadi Anda tidak perlu mengambil alamatnya. Itu menciptakan referensi ganda, tampaknya untuk tipe berukuran nol (dengan demikian nilai ajaib 1).
Shepmaster
Ini tidak persis menjawab pertanyaan Anda, tetapi Anda mungkin ingin:let ptr = foo as *const fn() as *const c_void;
Peter Hall

Jawaban:

3

Jawaban ini didasarkan pada balasan pada laporan bug yang dimotivasi oleh pertanyaan ini .

Setiap fungsi di Rust memiliki jenis item fungsi masing-masing , yang berbeda dari jenis item fungsi dari setiap fungsi lainnya. Karena alasan ini, turunan dari jenis item fungsi tidak perlu menyimpan informasi sama sekali - fungsi apa yang ditunjukkannya jelas dari jenisnya. Jadi variabel x in

let x = foo;

adalah variabel ukuran 0.

Tipe-tipe item fungsi secara implisit memaksa ke tipe-tipe pointer fungsi jika perlu. Variabel

let x: fn() = foo;

adalah pointer umum ke fungsi apa pun dengan tanda tangan fn(), dan karenanya perlu menyimpan pointer ke fungsi yang sebenarnya ditunjuknya, sehingga ukurannya xadalah ukuran pointer.

Jika Anda mengambil alamat suatu fungsi &foo,, Anda sebenarnya mengambil alamat dengan nilai sementara berukuran nol. Sebelum ini berkomitmen untuk rustrepo , temporaries berukuran nol digunakan untuk membuat alokasi pada stack, dan &foomengembalikan alamat alokasi itu. Karena komit ini, tipe berukuran nol tidak membuat alokasi lagi, dan alih-alih menggunakan alamat ajaib 1. Ini menjelaskan perbedaan antara versi Rust yang berbeda.

Sven Marnach
sumber
Ini masuk akal, tetapi saya tidak yakin bahwa itu umumnya perilaku yang diinginkan karena didasarkan pada asumsi yang berbahaya. Di dalam kode Rust yang aman, tidak ada alasan untuk membedakan pointer ke nilai ZST - karena hanya ada satu nilai yang mungkin diketahui pada waktu kompilasi. Ini rusak setelah Anda perlu menggunakan nilai ZST di luar sistem tipe Rust, seperti di sini. Ini mungkin hanya memengaruhi fntipe item dan penutupan yang tidak menangkap dan bagi mereka yang ada solusinya, seperti dalam jawaban saya, tapi itu masih cukup mudah!
Peter Hall
Ok, saya belum membaca tanggapan yang lebih baru tentang masalah Github. Saya bisa mendapatkan segfault dengan kode itu tetapi, jika kode dapat menyebabkan segfault maka saya kira perilaku baru itu ok.
Peter Hall
Jawaban yang bagus @PeterHall Saya memikirkan hal yang sama, dan saya masih belum 100% pada subjek, tetapi setidaknya untuk variabel temporari dan stack lainnya, seharusnya tidak ada masalah dengan meletakkan semua nilai nol pada 0x1 karena kompiler tidak membuat menjamin tentang tata letak tumpukan, dan Anda tidak bisa menjamin keunikan pointer ke ZST. Hal ini berbeda, mengatakan, casting *const i32untuk *const c_voidyang, untuk pemahaman saya, masih dijamin untuk melestarikan identitas pointer.
trentcl
2

Apa yang _disimpulkan dan mengapa perilaku berubah?

Setiap kali Anda melakukan lemparan pointer mentah, Anda hanya dapat mengubah satu informasi (referensi atau pointer mentah; mutabilitas; jenis). Karena itu, jika Anda melakukan ini:

let ptr = &foo as *const _

karena Anda telah berubah dari referensi ke pointer mentah, tipe yang disimpulkan _ harus tidak berubah dan karenanya adalah tipe foo, yang merupakan tipe yang tidak dapat diekspresikan untuk fungsi tersebut foo.

Alih-alih melakukan itu, Anda bisa langsung melemparkan ke pointer fungsi, yang dapat diekspresikan dalam sintaks Rust:

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

Adapun mengapa itu telah berubah, itu sulit dikatakan. Ini bisa jadi bug di build malam. Layak melaporkannya - bahkan jika itu bukan bug, Anda mungkin akan mendapatkan penjelasan yang bagus dari tim penyusun tentang apa yang sebenarnya terjadi!

Peter Hall
sumber
1
Terima kasih, saya telah melaporkannya github.com/rust-lang/rust/issues/65499
Maciej Goszczycki
@MaciejGoszczycki Terima kasih telah melaporkan! Jawabannya benar-benar menjelaskan segalanya bagi saya - saya akan memposting jawaban berdasarkan tanggapan di sana.
Sven Marnach