转换产生无效指针的函数引用?


9

我正在跟踪第三方代码中的错误,并将其缩小到类似的范围。

use libc::c_void;

pub unsafe fn foo() {}

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

在稳定的1.38.0上运行,这会打印函数指针,但是beta(1.39.0-beta.6)和夜间返回'1'。(游乐场

_推断出什么,为什么行为发生了变化?

我认为正确的方法只是foo as *const c_void,但这不是我的代码。


我无法回答“为什么更改了”,但我同意您的观点,认为代码不正确。foo已经是一个函数指针,因此您不应该使用地址。这将创建一个双重引用,似乎是零大小的类型(因此为magic值1)。
Shepmaster

这不能完全回答您的问题,但是您可能想要:let ptr = foo as *const fn() as *const c_void;
彼得·霍尔

Answers:


3

该答案基于此问题对错误报告的答复。

Rust中的每个功能都有其单独的功能项类型,这不同于其他所有功能的功能项类型。因此,功能项类型的实例根本不需要存储任何信息-它指向的功能在其类型中是显而易见的。所以变量x in

let x = foo;

是大小为0的变量。

在必要时,函数项类型隐式强制为函数指针类型。变量

let x: fn() = foo;

是一个通用的指针,任何与签名功能fn(),因此需要存储的指针,它实际指向的功能,所以大小x是一个指针的大小。

如果使用函数的地址&foo,则实际上是在使用零大小的临时值的地址。在提交到rust仓库之前,零大小的临时文件用于在堆栈上创建分配,并&foo返回该分配的地址。自从提交以来,零大小的类型不再创建分配,而是使用魔术地址1。这解释了Rust不同版本之间的区别。


这是有道理的,但我不相信它通常是理想的行为,因为它是基于不稳定的假设建立的。在安全的Rust代码中,没有理由区分指向ZST值的指针-因为在编译时只有一个可能的值是已知的。一旦您需要在Rust类型系统之外使用ZST值(例如此处),此方法就会崩溃。它可能只影响fn项目类型和非捕获的闭包,对于那些存在解决方法的问题(如我的回答),但这仍然是一门必经之路!
彼得·霍尔,

好的,我还没有阅读有关Github问题的最新回复。我可以用该代码获得段错误,但是,如果代码可能导致段错误,那么我猜这种新行为是可以的。
彼得·霍尔,

好答案。@PeterHall我在想同样的事情,但我仍然没有100%地了解这个问题,但是至少对于临时变量和其他堆栈变量,将所有零大小的值都放在0x1应该没有问题,因为编译器不会保证有关堆栈布局,并且您无论如何也不能保证指向ZST的指针的唯一性。这是从,说不同,铸造*const i32*const c_void这,我的理解,仍保证保留指针的身份。
trentcl

2

_推断出什么,为什么行为发生了变化?

每次执行原始指针强制转换时,您只能更改一条信息(引用或原始指针;可变性;类型)。因此,如果执行此强制转换:

let ptr = &foo as *const _

由于您已从引用更改为原始指针,因此,为推断的类型_ 必须保持不变,因此为的类型foo,这是该函数的某种无法表达的类型foo

不用这样做,您可以直接转换为函数指针,该指针在Rust语法中可以表示:

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

至于它为什么改变了,这很难说。这可能是夜间构建中的错误。值得报告 -即使它不是错误,您也可能会从编译器团队获得有关实际情况的很好的解释!



@MaciejGoszczycki感谢您的举报!这些回答实际上确实为我清除了一切-我将根据那里的回答发布答案。
Sven Marnach
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.