[英]Safe to pass struct without repr(C) to C API?
根據Shepmaster的評論建議提出新問題
假設一個庫定義了一個沒有#[repr(C)]
。 如果需要將結構傳遞給C API,有沒有一種安全的方法呢? 我是否應該擔心Rust編譯器會以C API不期望的方式更改結構的內存布局?
我見過一些圖書館這樣做。 根據我的經驗, 他們在將結構傳遞給C函數時使用mem::transmute
。 transmute
是否會以某種方式消除#[repr(C)]
?
如果我是定義結構的人,我不會問。 我只想添加#[repr(C)]
。 問題是我想使用我無法控制的庫中的結構。
老問題
我一直在使用cgmath-rs ,如果cgmath 被放棄 ,我可能會切換到nalgebra 。
在這兩個庫的源代碼中,我沒有在向量和矩陣結構上看到#[repr(C)]
。 那么這些庫如何與OpenGL API兼容? 當您將指針傳遞給向量和矩陣時,OpenGL需要一定的內存布局。 我的理解是,沒有#[repr(C)]
,Rust結構的內存布局是未定義的。
至少在一個例子中 ,我看到mem::transmute
在將它們交給OpenGL之前應用於這些結構。 我可能會誤解transmute
,但該函數似乎保留了struct的內存布局。 所以,如果布局是錯誤的,首先,它仍然是錯誤的之后transmute
,是否正確?
我也考慮過vecmath 。 但看起來,根據設計,vecmath缺乏輔助函數來生成旋轉矩陣等。 沒錯,我可以實現這些,但不必這樣做會很好。 無論如何,vecmath的設計是否通過使用數組而不是結構來避免內存布局的問題?
我見過一些圖書館這樣做。 根據我的經驗,他們在將結構傳遞給C函數時使用mem :: transmute。 轉化是否會以某種方式消除#[repr(C)]的需要?
沒有。
......但它很復雜。
如果你有一個&Foo,並且你將它傳遞給C並且根本不編輯它 ,那么將你的&Foo轉換為* const c_void並將其傳遞給ac調用是完全有效的,例如:
let fp = &foo as *const Foo as *const c_void
你可能會看到人們這樣做是一步,使用transmute;
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)的缺失,是的,你可以期待生銹與你的結構布局混亂; 這通常與drop標志有關,並且可以作為https://github.com/rust-lang/rfcs/pull/320的一部分解決,因為drop flags目前是內存布局到結構的唯一有形的不同; 但由於RFC沒有明確涵蓋,我不會屏住呼吸。
即。 tldr; 如果你需要傳遞一個結構並在C中修改它,它需要repr(C); 如果沒有,它將無法工作**。
轉化被用於其他原因,完全與此無關。
** - >好吧,它可能有效,但你真正打的是未定義的行為。 如果您正在使用的庫工作,可能是因為它恰好起作用。 生銹很多東西都是如此; 例如。 在某些情況下可變別名...但它確實意味着破壞了代碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.