< 返回版块

aircloud 发表于 2021-08-22 18:34

Tags:ffi

目前我在处理 rust ffi 的一些问题,有一个诉求是希望在 rust 侧判断 c 传来的指针是否是零指针。

我使用 Mac 进行测试,rustc 1.48.0(最新的 nightly 也是一样的), 目前遇到了一个比较奇怪的例子。我这里提供了一个 demo:

#include "stdlib.h"
#include <stdint.h>

typedef void (*PFNStop)();

PFNStop test_c() {
    return (void*) 0; 
}

extern crate libc;

pub type CCallbackType = extern "C" fn();

extern "C" {
    fn test_c() -> CCallbackType;
}

fn main() {
    let output: CCallbackType = unsafe { test_c() };
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as u32, output as u32 == 0);
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as i32, output as i32 == 0);
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as u64, output as u64 == 0);
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as i64, output as i64 == 0);
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as u128, output as u128 == 0);
}

extern crate cc;

fn main() {
    cc::Build::new()
        .file("src/double.c")
        .compile("libdouble.a");
}

cargo run --release 打出来的结果为:

c_func_ptr_inner is: 0 equal 0 true
c_func_ptr_inner is: 0 equal 0 true
c_func_ptr_inner is: 0 equal 0 false
c_func_ptr_inner is: 0 equal 0 false
c_func_ptr_inner is: 0 equal 0 false

debug 模式下倒是符合预期的:

c_func_ptr_inner is: 0 equal 0 true
c_func_ptr_inner is: 0 equal 0 true
c_func_ptr_inner is: 0 equal 0 true
c_func_ptr_inner is: 0 equal 0 true
c_func_ptr_inner is: 0 equal 0 true

我对于 release 模式下的这种表现,感到非常奇怪(明明是 0 却不等于 0),想知道有没有了解的同学可以帮忙解释一下?

评论区

写评论
uno 2021-08-23 10:03

在文档里说了的,FFI的可空指针,用Option来表示。

pub type CCallbackType = extern "C" fn();

extern "C" {
    fn test_c() -> Option<CCallbackType>;
}

fn main() {
    let output = unsafe { test_c() };
    println!("{:?}", output);
    // println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as u32, output as u32 == 0);
    // println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as i32, output as i32 == 0);
    // println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as u64, output as u64 == 0);
    // println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as i64, output as i64 == 0);
    // println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output as u128, output as u128 == 0);
}

peacess 2021-08-22 22:39

这个问题,很有意思。 不过指针到整数的转换,这个部分如果是明确unsafe的,会感觉好一些, 可以再试一下raw pointer时,is_null是怎么做的,看结构怎么样

chirsz-ever 2021-08-22 19:49

解释(个人理解,没有严格验证过):

Rust 里的函数指针和引用一样是非空类型,所以在 release 下会假设为非空值来优化,与 0 比较时返回 false

楼主使用的应该是 64 位机器,所以将函数指针转换为小于 64 位宽度的整数时需要截断,这种情况就不能简单地做非空值优化,所以在 release 下转换为 u8 u16 u32 进行了实际转换和比较,得到了正确的 true 结果。

解决方案:C 函数声明里返回类型改为 Option<CCallbackType>

pub type CCallbackType = extern "C" fn();

extern "C" {
    fn test_c() -> Option<CCallbackType>;
}

fn main() {
    let output = unsafe { test_c() };
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output.map_or(0, |p|p as u32), output.map_or(0, |p|p as u32) == 0);
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output.map_or(0, |p|p as i32), output.map_or(0, |p|p as i32) == 0);
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output.map_or(0, |p|p as u64), output.map_or(0, |p|p as u64) == 0);
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output.map_or(0, |p|p as i64), output.map_or(0, |p|p as i64) == 0);
    println!("c_func_ptr_inner is: {:?} equal 0 {:?}", output.map_or(0, |p|p as u128), output.map_or(0, |p|p as u128) == 0);
}

这时判断空可以直接用 output.is_none(),还可以用模式匹配。

gwy15 2021-08-22 19:41

跟 FFI 没有关系,rust 在 release 的情况下直接对 output as i64 == 0 做了优化,认为 output as i64 是函数指针,一定不为 0,所以直接在编译时优化 output as i64 == 0 为 false。

一个最小可复现例子:https://godbolt.org/z/sYsTvf79f

汇编:https://imgtu.com/i/hpF69x

至于 i8 和 i32 为啥没被优化我就不知道了

1 共 4 条评论, 1 页