为什么用 == 比较两个看似相等的指针会返回 false? [英] Why can comparing two seemingly equal pointers with == return false?

查看:54
本文介绍了为什么用 == 比较两个看似相等的指针会返回 false?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想测试两个 Rc 类型的对象是否包含一个具体类型的相同实例,所以我比较指向 Rc 内部对象的指针是否相等.如果所有代码都驻留在同一个 crate 中,它似乎可以正常工作,但在涉及多个 crate 时会失败.

I want to test if two objects of type Rc<Trait> contain the same instance of a concrete type, so I compare pointers to the objects inside Rc for equality. It seems to work correctly if all the code resides in the same crate but fails when multiple crates are involved.

在 Rust 1.17 中,函数 Rc添加了 ::ptr_eq,从 Rust 1.31 开始,它表现出与此问题中使用的手动指针比较相同的跨箱问题.

In Rust 1.17 the function Rc::ptr_eq was added, which as of Rust 1.31, exhibits the same cross-crate issue as the manual pointer comparison used in this question.

这是 crate mcve (src/lib.rs) 的实现:

This is the implementation of crate mcve (src/lib.rs):

use std::rc::Rc;

pub trait ObjectInterface {}

pub type Object = Rc<ObjectInterface>;

pub type IntObject = Rc<i32>;

impl ObjectInterface for i32 {}

/// Test if two Objects refer to the same instance
pub fn is_same(left: &Object, right: &Object) -> bool {
    let a = left.as_ref() as *const _;
    let b = right.as_ref() as *const _;
    let r = a == b;
    println!("comparing: {:p} == {:p} -> {}", a, b, r);
    r
}

pub struct Engine {
    pub intval: IntObject,
}

impl Engine {
    pub fn new() -> Engine {
        Engine {
            intval: Rc::new(42),
        }
    }

    pub fn run(&mut self) -> Object {
        return self.intval.clone();
    }
}

我使用以下代码 (tests/testcases.rs) 测试实现:

I test the implementation with the following code (tests/testcases.rs):

extern crate mcve;

use mcve::{is_same, Engine, Object};

#[test]
fn compare() {
    let mut engine = Engine::new();

    let a: Object = engine.intval.clone();
    let b = a.clone();
    assert!(is_same(&a, &b));

    let r = engine.run();
    assert!(is_same(&r, &a));
}

运行测试结果如下:

comparing: 0x7fcc5720d070 == 0x7fcc5720d070 -> true
comparing: 0x7fcc5720d070 == 0x7fcc5720d070 -> false
thread 'compare' panicked at 'assertion failed: is_same(&r, &a)'

虽然指针看起来相同,但比较运算符 == 怎么可能返回 false?

How is it possible that the comparison operator == returns false although the pointers seem to be the same?

一些观察:

  • 当两个对象(ab)位于同一个 crate 中时,比较返回 true.但是,当一个对象 (r) 由函数 Engine::run 返回时,比较返回 false,该函数在另一个对象中定义板条箱.
  • 当我将测试函数放入 lib.rs 时,测试正确通过.
  • 这个问题可以通过定义struct Engine { intval: Object }来解决,但我仍然对为什么感兴趣.
  • The comparison returns true when both objects (a and b) live in the same crate. However, the comparison returns false when one of the objects (r) was returned by the function Engine::run, which is defined in another crate.
  • The test correctly passes when I put the test function inside lib.rs.
  • The problem can be fixed by defining struct Engine { intval: Object }, but I'm still interested in the why.

推荐答案

什么时候是指针"而不是指针"?当它是一个胖指针时.ObjectInterface 是一个 trait,这意味着 &dyn ObjectInterface 是一个 trait 对象.Trait 对象由两个机器指针组成:一个用于具体数据,一个用于vtable,一组用于具体值的 trait 的具体实现.这个双指针称为胖指针.

When is a "pointer" not a "pointer"? When it's a fat pointer. ObjectInterface is a trait, which means that &dyn ObjectInterface is a trait object. Trait objects are composed of two machine pointers: one for the concrete data and one for the vtable, a set of the specific implementations of the trait for the concrete value. This double pointer is called a fat pointer.

使用夜间编译器和 std::raw::TraitObject,可以看出区别:

Using a nightly compiler and std::raw::TraitObject, you can see the differences:

#![feature(raw)]

use std::{mem, raw};

pub fn is_same(left: &Object, right: &Object) -> bool {
    let a = left.as_ref() as *const _;
    let b = right.as_ref() as *const _;
    let r = a == b;
    println!("comparing: {:p} == {:p} -> {}", a, b, r);

    let raw_object_a: raw::TraitObject = unsafe { mem::transmute(left.as_ref()) };
    let raw_object_b: raw::TraitObject = unsafe { mem::transmute(right.as_ref()) };
    println!(
        "really comparing: ({:p}, {:p}) == ({:p}, {:p})",
        raw_object_a.data, raw_object_a.vtable,
        raw_object_b.data, raw_object_b.vtable,
    );

    r
}

comparing: 0x101c0e010 == 0x101c0e010 -> true
really comparing: (0x101c0e010, 0x1016753e8) == (0x101c0e010, 0x1016753e8)
comparing: 0x101c0e010 == 0x101c0e010 -> false
really comparing: (0x101c0e010, 0x101676758) == (0x101c0e010, 0x1016753e8)

事实证明(至少在 Rust 1.22.1 中)每个代码生成单元都创建了一个单独的 vtable!这解释了为什么当它们都在同一个模块中时它可以工作.有关于这是否是错误的积极讨论.

It turns out that (at least in Rust 1.22.1) each code generation unit creates a separate vtable! This explains why it works when it's all in the same module. There's active discussion on if this is a bug or not.

当您使用 #[inline] 注释 newrun 函数时,消费者将使用该 vtable.

When you annotate the new and run functions with #[inline] the consumers will use that vtable.

作为 Francis Gagné 说:

你可以把as *const _改为as *const _ as *const (),如果你只关心值的地址.

You can change as *const _ to as *const _ as *const () to turn the fat pointer into a regular pointer if you only care about the value's address.

这可以使用 std::ptr 清晰表达::eq:

This can be cleanly expressed using std::ptr::eq:

use std::ptr;

pub fn is_same(left: &Object, right: &Object) -> bool {
    let r = ptr::eq(left.as_ref(), right.as_ref());
    println!("comparing: {:p} == {:p} -> {}", left, right, r);
    r
}

这篇关于为什么用 == 比较两个看似相等的指针会返回 false?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆