soword科技言
永久公益免费API接口
提供永久免费的API接口,查看更多API接口,如果您有其他免费API资源,请联系我们,造福人类。
提供商务开发:小程序,系统,APP
定制开发,免费评估,免费咨询,价格便宜,售后保障,前往开发服务中心联系开发客服中心
Wat代码:Rust允许通过空指针调用函数

我在Rust中尝试了函数指针魔术,最后得到了一个代码片段,对于它为什么编译甚至更多,为什么运行,我完全没有解释。

fn foo() {
println!("This is really weird...");}fn caller<F>() where F: FnMut() {
let closure_ptr = 0 as *mut F;
let closure = unsafe { &mut *closure_ptr };
closure();}fn create<F>(_: F) where F: FnMut() {
let func_ptr = caller::<F> as fn();
func_ptr();}fn main() {
create(foo);

create(|| println!("Okay..."));

let val = 42;
create(|| println!("This will seg fault: {}", val));}

我无法解释为什么 foo通过将nullptrin 强制转换caller(...)为类型F的实例来进行调用。我本以为只能通过相应的函数指针来调用函数,但是鉴于指针本身为null,显然不是这种情况。话虽如此,看来我显然误解了Rust的类型系统的重要部分。

有人有答案吗?

该程序实际上从不构造指向任何东西的函数指针,而是caller-总是直接调用foo这两个闭包

每个Rust函数,无论是闭包还是fn项目,都具有唯一的匿名类型。这种类型的实现FnFnMutFnOnce特征,适当的。项的匿名类型fn为零大小,就像没有捕获的闭包类型一样。

因此,表达式create(foo)实例化create的参数Ffoo的类型-这不是函数指针类型fn(),但匿名,零大小的类型只是为了foo在错误消息中,rustc称为这种类型fn() {foo},因为您可以看到此错误消息

在内部create::<fn() {foo}>(使用错误消息中的名称),该表达式caller::<F> as fn()构造了一个指向的函数指针caller::<fn() {foo}>调用此函数指针与caller::<F>()直接调用相同,这也是整个程序中唯一的函数指针。

最后,在caller::<fn() {foo}>表达式中closure()减掉FnMut::call_mut(closure)因为closure具有类型&mut Fwhere F只是零大小的类型fn() {foo},所以它本身0closure根本就不会用1,并且程序会foo直接调用

同样的逻辑也适用于闭包|| println!("Okay..."),它foo具有一个匿名的零尺寸类型,这次称为[closure@src/main.rs:2:14: 2:36]

第二个闭包并不是那么幸运-它的类型不是零大小的,因为它必须包含对变量的引用val这一次,FnMut::call_mut(closure)实际上需要取消引用closure才能完成其工作。因此它崩溃了2

类型fn foo() {...}不是函数指针fn(),实际上是特定于的唯一类型foo只要您携带该类型(此处为F),编译器就知道如何调用它而无需任何额外的指针(此类类型的值不携带数据)。不捕获任何内容的闭包以相同的方式工作。仅当最后一个闭包尝试查找时,它才会出现错误,val因为您将0指针假定放置在了某个位置(大概)val

您可以使用观察到这一点size_of,在前两个调用中,的大小closure为零,但是在最后一个调用中,在闭包中捕获到的东西的大小为8(至少在操场上)。如果大小为0,则程序不必从NULL指针加载任何内容

NULL指向引用指针的有效转换仍然是未定义的行为,但是由于类型为shenanigans而不是由于内存访问shenanigans:具有实际上NULL是非法的引用,因为类型之类的内存布局Option<&T>依赖于以下假设:参考永远不会NULL这是一个如何出错的示例:

unsafe fn null<T>(_: T) -> &'static mut T {
&mut *(0 as *mut T)}fn foo() {
println!("Hello, world!");}fn main() {
unsafe {
let x = null(foo);
x(); // prints "Hello, world!"
let y = Some(x);
println!("{:?}", y.is_some()); // prints "false", y is None!
}}

尽管这完全取决于UB,但这是我认为在两种情况下可能会发生的情况:

  1. 类型F是没有数据的闭包。这等效于一个功能,这意味着它F是一个功能项。这意味着编译器可以优化对an的任何调用,以优化F对产生的任何函数的调用F(而无需创建函数指针)。对于这些东西的不同名称的例子。

  2. 编译器会看到它val始终为42,因此可以将其优化为一个常数。如果是这种情况,那么传入的闭包create仍然是没有捕获项目的闭包,因此我们可以遵循#1中的思想。

另外,我说这是UB,但是请注意关于UB的一些重要方面:如果调用UB,并且编译器以一种意想不到的方式利用它,它不是在试图弄乱您,而是在尝试优化代码。毕竟,UB是关于编译器未进行优化的事情,因为您违反了它的某些期望。因此,编译器以这种方式进行优化是完全合乎逻辑的。编译器不采用这种方式进行优化,而是利用UB,这完全是合乎逻辑的。

这是“有效的”,因为fn() {foo}和第一个闭包都是零大小的类型。扩展答案:

如果此程序最终在Miri(未定义行为检查器)中执行,则最终将失败,因为已取消引用NULL指针。即使对于零大小的类型,也无法取消引用NULL指针。但是,未定义的行为可以做任何事情,因此编译器对此行为不做任何保证,这意味着它可以在以后的Rust版本中破坏。

error: Undefined Behavior: memory access failed: 0x0 is not a valid pointer
--> src/main.rs:7:28
 |7| let closure = unsafe { &mut *closure_ptr };
 |^^^^^^^^^^^^^^^^^ memory access failed: 0x0 is not a valid pointer
 |
 = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
 = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
 
 = note: inside `caller::<fn() {foo}>` at src/main.rs:7:28note: inside `create::<fn() {foo}>` at src/main.rs:13:5
--> src/main.rs:13:5
 |13 | func_ptr();
 | ^^^^^^^^^^note: inside `main` at src/main.rs:17:5
--> src/main.rs:17:5
 |17 | create(foo);
 | ^^^^^^^^^^^

这个问题可以很容易地通过写来解决let closure_ptr = 1 as *mut F;,然后它只会在第22行出现第二个闭包而失败。

error: Undefined Behavior: inbounds test failed: 0x1 is not a valid pointer
--> src/main.rs:7:28
 |7| let closure = unsafe { &mut *closure_ptr };
 |^^^^^^^^^^^^^^^^^ inbounds test failed: 0x1 is not a valid pointer
 |
 = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
 = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
 
 = note: inside `caller::<[closure@src/main.rs:22:12: 22:55 val:&i32]>` at src/main.rs:7:28note: inside `create::<[closure@src/main.rs:22:12: 22:55 val:&i32]>` at src/main.rs:13:5
--> src/main.rs:13:5
 |13 | func_ptr();
 | ^^^^^^^^^^note: inside `main` at src/main.rs:22:5
--> src/main.rs:22:5
 |22 | create(|| println!("This will seg fault: {}", val));
 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

为什么它没有抱怨foo|| println!("Okay...")好吧,因为它们不存储任何数据。当指代一个函数时,您不会得到一个函数指针,而是一个表示该特定函数的零大小类型-这有助于进行单态化,因为每个函数都是不同的。可以从对齐的悬挂指针创建不存储任何数据的结构。

但是,如果您明确地说该函数是函数指针,create::<fn()>(foo)则该程序将停止工作。



2023-03-22 10:04:19

新人小程序+APP定制199元起


发放福利,助力中小企业发展,真正在互联网中受益

点击询问定制

广告服务展示