如何以字节为单位获取指针偏移量?
How to get pointer offset in bytes?
虽然 Rust 中的原始指针具有 offset
方法,但这只会增加指针的大小。如何以字节为单位访问指针?
在 C:
中类似这样的东西
var_offset = (typeof(var))((char *)(var) + offset);
TL;DR:根据 RFC-2582.
,此答案调用未定义行为
In particular, references must be aligned and dereferencable, even when they are created and never used.
也有讨论指出,由于 getelementptr inbounds
的使用,字段访问本身强加了提议的 &raw
未解决的额外要求,请参阅 offsetof
麻烦 在 RFC 的底部。
从 answer 我链接到你之前的问题:
macro_rules! offset_of {
($ty:ty, $field:ident) => {
// Undefined Behavior: dereferences a null pointer.
// Undefined Behavior: accesses field outside of valid memory area.
unsafe { &(*(0 as *const $ty)).$field as *const _ as usize }
}
}
fn main() {
let p: *const Baz = 0x1248 as *const _;
let p2: *const Foo = ((p as usize) - offset_of!(Foo, memberB)) as *const _;
println!("{:p}", p2);
}
我们在p2
的计算中可以看到,指针可以无痛地转换为整数(此处为usize
),对其进行运算,然后将结果转换回一个指针。
isize
和 usize
是通用字节大小的指针类型:)
如果 RFC-2582 被接受,这个 offset_of!
的实现是我最好的选择:
macro_rules! offset_of {
($ty:ty, $field:ident) => {
unsafe {
// Create correctly sized storage.
//
// Note: `let zeroed: $ty = ::std::mem::zeroed();` is incorrect,
// a zero pattern is not always a valid value.
let buffer = ::std::mem::MaybeUninit::<$ty>::uninit();
// Create a Raw reference to the storage:
// - Alignment does not matter, though is correct here.
// - It safely refers to uninitialized storage.
//
// Note: using `&raw const *(&buffer as *const _ as *const $ty)`
// is incorrect, it creates a temporary non-raw reference.
let uninit: &raw const %ty = ::std::mem::transmute(&buffer);
// Create a Raw reference to the field:
// - Alignment does not matter, though is correct here.
// - It points within the memory area.
// - It safely refers to uninitialized storage.
let field = &raw const uninit.$field;
// Compute the difference between pointers.
(field as *const _ as usize) - (uninit as *const_ as usize)
}
}
}
我对每一步都做了评论,说明了我认为它们是合理的原因,以及为什么有些替代方案不合理——我在不安全代码中大力鼓励这样做——希望没有遗漏任何东西。
感谢@Matthieu M. 的回答,这可以使用指针偏移来完成,这里是一个可重用的宏:
macro_rules! offset_of {
($ty:ty, $field:ident) => {
&(*(0 as *const $ty)).$field as *const _ as usize
}
}
macro_rules! check_type_pair {
($a:expr, $b:expr) => {
if false {
let _type_check = if false {$a} else {$b};
}
}
}
macro_rules! parent_of_mut {
($child:expr, $ty:ty, $field:ident) => {
{
check_type_pair!(&(*(0 as *const $ty)).$field, &$child);
let offset = offset_of!($ty, $field);
&mut *(((($child as *mut _) as usize) - offset) as *mut $ty)
}
}
}
macro_rules! parent_of {
($child:expr, $ty:ty, $field:ident) => {
{
check_type_pair!(&(*(0 as *const $ty)).$field, &$child);
let offset = offset_of!($ty, $field);
&*(((($child as *const _) as usize) - offset) as *const $ty)
}
}
}
这样,当我们在结构中有一个字段时,我们可以像这样获取父结构:
fn some_method(&self) {
// Where 'self' is ParentStruct.field,
// access ParentStruct instance.
let parent = unsafe { parent_of!(self, ParentStruct, field) };
}
宏 check_type_pair
有助于避免 self
和 ParentStruct.field
类型不同的简单错误。然而,当一个结构中的两个不同成员具有相同类型时,它并不是万无一失的。
虽然 Rust 中的原始指针具有 offset
方法,但这只会增加指针的大小。如何以字节为单位访问指针?
在 C:
中类似这样的东西var_offset = (typeof(var))((char *)(var) + offset);
TL;DR:根据 RFC-2582.
,此答案调用未定义行为In particular, references must be aligned and dereferencable, even when they are created and never used.
也有讨论指出,由于 getelementptr inbounds
的使用,字段访问本身强加了提议的 &raw
未解决的额外要求,请参阅 offsetof
麻烦 在 RFC 的底部。
从 answer 我链接到你之前的问题:
macro_rules! offset_of {
($ty:ty, $field:ident) => {
// Undefined Behavior: dereferences a null pointer.
// Undefined Behavior: accesses field outside of valid memory area.
unsafe { &(*(0 as *const $ty)).$field as *const _ as usize }
}
}
fn main() {
let p: *const Baz = 0x1248 as *const _;
let p2: *const Foo = ((p as usize) - offset_of!(Foo, memberB)) as *const _;
println!("{:p}", p2);
}
我们在p2
的计算中可以看到,指针可以无痛地转换为整数(此处为usize
),对其进行运算,然后将结果转换回一个指针。
isize
和 usize
是通用字节大小的指针类型:)
如果 RFC-2582 被接受,这个 offset_of!
的实现是我最好的选择:
macro_rules! offset_of {
($ty:ty, $field:ident) => {
unsafe {
// Create correctly sized storage.
//
// Note: `let zeroed: $ty = ::std::mem::zeroed();` is incorrect,
// a zero pattern is not always a valid value.
let buffer = ::std::mem::MaybeUninit::<$ty>::uninit();
// Create a Raw reference to the storage:
// - Alignment does not matter, though is correct here.
// - It safely refers to uninitialized storage.
//
// Note: using `&raw const *(&buffer as *const _ as *const $ty)`
// is incorrect, it creates a temporary non-raw reference.
let uninit: &raw const %ty = ::std::mem::transmute(&buffer);
// Create a Raw reference to the field:
// - Alignment does not matter, though is correct here.
// - It points within the memory area.
// - It safely refers to uninitialized storage.
let field = &raw const uninit.$field;
// Compute the difference between pointers.
(field as *const _ as usize) - (uninit as *const_ as usize)
}
}
}
我对每一步都做了评论,说明了我认为它们是合理的原因,以及为什么有些替代方案不合理——我在不安全代码中大力鼓励这样做——希望没有遗漏任何东西。
感谢@Matthieu M. 的回答,这可以使用指针偏移来完成,这里是一个可重用的宏:
macro_rules! offset_of {
($ty:ty, $field:ident) => {
&(*(0 as *const $ty)).$field as *const _ as usize
}
}
macro_rules! check_type_pair {
($a:expr, $b:expr) => {
if false {
let _type_check = if false {$a} else {$b};
}
}
}
macro_rules! parent_of_mut {
($child:expr, $ty:ty, $field:ident) => {
{
check_type_pair!(&(*(0 as *const $ty)).$field, &$child);
let offset = offset_of!($ty, $field);
&mut *(((($child as *mut _) as usize) - offset) as *mut $ty)
}
}
}
macro_rules! parent_of {
($child:expr, $ty:ty, $field:ident) => {
{
check_type_pair!(&(*(0 as *const $ty)).$field, &$child);
let offset = offset_of!($ty, $field);
&*(((($child as *const _) as usize) - offset) as *const $ty)
}
}
}
这样,当我们在结构中有一个字段时,我们可以像这样获取父结构:
fn some_method(&self) {
// Where 'self' is ParentStruct.field,
// access ParentStruct instance.
let parent = unsafe { parent_of!(self, ParentStruct, field) };
}
宏 check_type_pair
有助于避免 self
和 ParentStruct.field
类型不同的简单错误。然而,当一个结构中的两个不同成员具有相同类型时,它并不是万无一失的。