将没有 repr(C) 的结构传递给 C API 是否安全?
Safe to pass struct without repr(C) to C API?
根据 Shepmaster 在评论中的建议提出的新问题
假设一个库定义了一个没有 #[repr(C)]
的结构。如果需要将结构传递给 C API,是否有安全的方法?我是否应该担心 Rust 编译器会以 C API 不期望的方式更改结构的内存布局?
我见过一些图书馆这样做。根据我的经验,they use mem::transmute
是时候将结构传递给 C 函数了。 transmute
是否以某种方式消除了对 #[repr(C)]
的需求?
如果我是定义结构的人,我就不会问了。我只想添加 #[repr(C)]
。问题是我想使用我无法控制的库中的结构。
老问题
我一直在使用 cgmath-rs, and I might switch to nalgebra if cgmath is being abandoned。
在这两个库的源代码中,我没有在向量和矩阵结构上看到 #[repr(C)]
。那么这些库如何与 OpenGL API 兼容?当您将指向向量和矩阵的指针传递给 OpenGL 时,它需要特定的内存布局。据我了解,如果没有 #[repr(C)]
,Rust 结构的内存布局是未定义的。
在 at least one example 中,我看到 mem::transmute
在将它们交给 OpenGL 之前被应用于这些结构。我可能误解了 transmute
,但该函数似乎保留了结构的内存布局。所以如果布局一开始就错了,transmute
之后还是错的,对吗?
我也考虑过vecmath。但是,根据设计,vecmath 似乎缺少生成旋转矩阵等的辅助函数。是的,我可以实现这些,但最好不要这样做。无论如何,vecmath的设计是否通过使用数组而不是结构来避免内存布局的问题?
I've seen some libraries do this. In my experience, they use mem::transmute when it's time to pass the struct to a C function. Does transmute somehow eliminate the need for #[repr(C)]?
没有
...但它很复杂。
如果您有一个 &Foo,并且将它传递给 C 并且 根本不编辑它,那么将您的 &Foo 转换为 *const [=49] 是完全有效的=] 并将其传递给 c 调用,例如:
let fp = &foo as *const Foo as *const c_void
您可能会看到人们将此作为一个步骤,使用转化;
unsafe { ffi_call(transmute(&foo), ...) }
...但重要的是要了解转换调用不会修改内存布局;但它确实 消耗值 。
因此,例如,此代码稍后可能会导致段错误:
{
let foo = Foo { ... }
unsafe { ffi_call(&foo as _ as *const c_void); }
}
这是因为指针&foo指向foo;但是 foo 在范围结束后不再存在;因此,如果稍后使用它(例如 ffi 调用保留引用),则会导致段错误。
您可能认为装箱(即移动到堆)解决了这个问题:
let foo = Box::new(Foo { ... })
unsafe { ffi_call(&*foo as *const c_void); }
...但事实并非如此;因为当 Box 离开范围时,它会被丢弃。但是,因为转化会移动值;此代码安全地将 foo 实例移动到 ffi 调用中以供以后使用;请注意,如果您稍后不恢复该值,这是内存泄漏:
let foo = Box::new(Foo { ... })
unsafe { ffi_call(transmute(foo)); }
...但是不使用 transmute() 将解决缺少 repr(C) 的问题,是的,您可以预期生锈会弄乱您的结构布局;这通常与丢弃标志有关,并且 可能 作为 https://github.com/rust-lang/rfcs/pull/320 的一部分进行解析,因为丢弃标志目前是内存布局与结构唯一明显的区别;但由于该 RFC 中未明确涵盖,因此我不会屏住呼吸。
即。 tldr;如果你需要传递一个struct并在C中修改它,它需要repr(C);如果没有,它将不起作用**。
转化是出于其他原因,与此完全无关。
** --> 好吧,它可能有效,但你真正碰到的是未定义的行为。如果您使用的库有效,可能是因为它恰好有效。许多生锈的东西都是如此。例如。在某些情况下可变别名...但它确实意味着损坏的代码。
根据 Shepmaster 在评论中的建议提出的新问题
假设一个库定义了一个没有 #[repr(C)]
的结构。如果需要将结构传递给 C API,是否有安全的方法?我是否应该担心 Rust 编译器会以 C API 不期望的方式更改结构的内存布局?
我见过一些图书馆这样做。根据我的经验,they use mem::transmute
是时候将结构传递给 C 函数了。 transmute
是否以某种方式消除了对 #[repr(C)]
的需求?
如果我是定义结构的人,我就不会问了。我只想添加 #[repr(C)]
。问题是我想使用我无法控制的库中的结构。
老问题
我一直在使用 cgmath-rs, and I might switch to nalgebra if cgmath is being abandoned。
在这两个库的源代码中,我没有在向量和矩阵结构上看到 #[repr(C)]
。那么这些库如何与 OpenGL API 兼容?当您将指向向量和矩阵的指针传递给 OpenGL 时,它需要特定的内存布局。据我了解,如果没有 #[repr(C)]
,Rust 结构的内存布局是未定义的。
在 at least one example 中,我看到 mem::transmute
在将它们交给 OpenGL 之前被应用于这些结构。我可能误解了 transmute
,但该函数似乎保留了结构的内存布局。所以如果布局一开始就错了,transmute
之后还是错的,对吗?
我也考虑过vecmath。但是,根据设计,vecmath 似乎缺少生成旋转矩阵等的辅助函数。是的,我可以实现这些,但最好不要这样做。无论如何,vecmath的设计是否通过使用数组而不是结构来避免内存布局的问题?
I've seen some libraries do this. In my experience, they use mem::transmute when it's time to pass the struct to a C function. Does transmute somehow eliminate the need for #[repr(C)]?
没有
...但它很复杂。
如果您有一个 &Foo,并且将它传递给 C 并且 根本不编辑它,那么将您的 &Foo 转换为 *const [=49] 是完全有效的=] 并将其传递给 c 调用,例如:
let fp = &foo as *const Foo as *const c_void
您可能会看到人们将此作为一个步骤,使用转化;
unsafe { ffi_call(transmute(&foo), ...) }
...但重要的是要了解转换调用不会修改内存布局;但它确实 消耗值 。
因此,例如,此代码稍后可能会导致段错误:
{
let foo = Foo { ... }
unsafe { ffi_call(&foo as _ as *const c_void); }
}
这是因为指针&foo指向foo;但是 foo 在范围结束后不再存在;因此,如果稍后使用它(例如 ffi 调用保留引用),则会导致段错误。
您可能认为装箱(即移动到堆)解决了这个问题:
let foo = Box::new(Foo { ... })
unsafe { ffi_call(&*foo as *const c_void); }
...但事实并非如此;因为当 Box 离开范围时,它会被丢弃。但是,因为转化会移动值;此代码安全地将 foo 实例移动到 ffi 调用中以供以后使用;请注意,如果您稍后不恢复该值,这是内存泄漏:
let foo = Box::new(Foo { ... })
unsafe { ffi_call(transmute(foo)); }
...但是不使用 transmute() 将解决缺少 repr(C) 的问题,是的,您可以预期生锈会弄乱您的结构布局;这通常与丢弃标志有关,并且 可能 作为 https://github.com/rust-lang/rfcs/pull/320 的一部分进行解析,因为丢弃标志目前是内存布局与结构唯一明显的区别;但由于该 RFC 中未明确涵盖,因此我不会屏住呼吸。
即。 tldr;如果你需要传递一个struct并在C中修改它,它需要repr(C);如果没有,它将不起作用**。
转化是出于其他原因,与此完全无关。
** --> 好吧,它可能有效,但你真正碰到的是未定义的行为。如果您使用的库有效,可能是因为它恰好有效。许多生锈的东西都是如此。例如。在某些情况下可变别名...但它确实意味着损坏的代码。