Đúc một tham chiếu hàm tạo ra một con trỏ không hợp lệ?


9

Tôi đang theo dõi một lỗi trong mã của bên thứ ba và tôi đã thu hẹp nó thành một cái gì đó dọc theo dòng.

use libc::c_void;

pub unsafe fn foo() {}

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

Chạy ổn định 1.38.0, điều này in con trỏ hàm, nhưng beta (1.39.0-beta.6) và trả về hàng đêm '1'. ( Sân chơi )

Điều gì _được suy luận và tại sao hành vi đã thay đổi?

Tôi giả sử cách chính xác để thực hiện điều này chỉ đơn giản là foo as *const c_void, nhưng đây không phải là mã của tôi.


Tôi không thể trả lời "tại sao nó lại thay đổi", nhưng tôi đồng ý với bạn rằng mã không chính xác để bắt đầu. foođã là một con trỏ hàm, vì vậy bạn không nên lấy địa chỉ của nó. Điều đó tạo ra một tham chiếu kép, dường như là một loại có kích thước bằng không (do đó là giá trị ma thuật 1).
Người quản lý

Điều này không trả lời chính xác câu hỏi của bạn, nhưng bạn có thể muốn:let ptr = foo as *const fn() as *const c_void;
Peter Hall

Câu trả lời:


3

Câu trả lời này dựa trên các câu trả lời trên báo cáo lỗi được thúc đẩy bởi câu hỏi này .

Mỗi chức năng trong Rust có loại mục chức năng riêng biệt, khác với loại mục chức năng của mọi chức năng khác. Vì lý do này, một thể hiện của loại mục chức năng hoàn toàn không cần lưu trữ bất kỳ thông tin nào - chức năng mà nó trỏ đến là rõ ràng từ loại của nó. Vậy biến x trong

let x = foo;

là một biến có kích thước 0.

Các loại mục chức năng ngầm ép buộc các loại con trỏ hàm khi cần thiết. Biến

let x: fn() = foo;

là một con trỏ chung cho bất kỳ hàm nào có chữ ký fn()và do đó cần lưu trữ một con trỏ tới hàm mà nó thực sự trỏ tới, vì vậy kích thước của xlà kích thước của một con trỏ.

Nếu bạn lấy địa chỉ của hàm, &foothực tế bạn đang lấy địa chỉ của giá trị tạm thời có kích thước bằng không. Trước khi cam kết với rustrepo , các tạm thời có kích thước bằng không được sử dụng để tạo phân bổ trên ngăn xếp và &footrả về địa chỉ của phân bổ đó. Vì cam kết này, các loại có kích thước bằng 0 không tạo phân bổ nữa và thay vào đó sử dụng địa chỉ ma thuật 1. Điều này giải thích sự khác biệt giữa các phiên bản khác nhau của Rust.


Điều này có ý nghĩa, nhưng tôi không tin rằng đó thường là hành vi mong muốn bởi vì nó được xây dựng trên một giả định bấp bênh. Trong mã Rust an toàn, không có lý do để phân biệt các con trỏ với giá trị của ZST - bởi vì chỉ có một giá trị có thể được biết tại thời điểm biên dịch. Điều này bị hỏng khi bạn cần sử dụng giá trị ZST bên ngoài hệ thống loại Rust, chẳng hạn như ở đây. Nó có thể chỉ ảnh hưởng đến fncác loại vật phẩm và các lần đóng cửa không bắt giữ và đối với những người có cách giải quyết, như trong câu trả lời của tôi, nhưng nó vẫn khá là một khẩu súng!
Hội trường Peter

Ok, tôi đã không đọc những phản hồi mới hơn về vấn đề Github. Tôi có thể nhận được một segfault với mã đó, nhưng, nếu mã có thể gây ra một segfault thì tôi đoán hành vi mới là ổn.
Hội trường Peter

Câu trả lời chính xác. @PeterHall Tôi đã suy nghĩ tương tự, và tôi vẫn không 100% về chủ đề này, nhưng ít nhất là đối với các biến tạm thời và các biến stack khác, sẽ không có vấn đề gì khi đặt tất cả các giá trị có kích thước 0 ở 0x1 vì trình biên dịch không đảm bảo về bố cục ngăn xếp và dù sao bạn cũng không thể đảm bảo tính duy nhất của con trỏ đối với ZST. Điều này khác với, nói, đúc một *const i32để *const c_voidđó, để hiểu biết của tôi, vẫn còn được bảo đảm giữ gìn bản sắc của con trỏ.
trentcl

2

Điều gì _được suy luận và tại sao hành vi đã thay đổi?

Mỗi khi bạn thực hiện một con trỏ thô, bạn chỉ có thể thay đổi một phần thông tin (tham chiếu hoặc con trỏ thô; khả năng biến đổi; loại). Do đó, nếu bạn làm diễn viên này:

let ptr = &foo as *const _

vì bạn đã thay đổi từ một tham chiếu thành một con trỏ thô, loại được suy ra cho _ phải không thay đổi và do đó là loại foo, một loại không thể diễn tả được cho hàm foo.

Thay vì làm điều đó, bạn có thể truyền trực tiếp vào một con trỏ hàm, có thể biểu thị theo cú pháp Rust:

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

Về lý do tại sao nó đã thay đổi, thật khó để nói. Nó có thể là một lỗi trong xây dựng hàng đêm. Nó đáng để báo cáo nó - ngay cả khi nó không phải là một lỗi, bạn có thể sẽ nhận được một lời giải thích tốt từ nhóm biên dịch về những gì đang thực sự xảy ra!


1
Cảm ơn tôi đã báo cáo nó github.com/rust-lang/rust/issues/65499
Maciej Goszczycki

@MaciejGoszczycki Cảm ơn bạn đã báo cáo! Các câu trả lời thực sự đã làm rõ mọi thứ cho tôi - Tôi sẽ đăng câu trả lời dựa trên các câu trả lời ở đó.
Sven Marnach
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.