Rust FFI指针越界未触发panic/段错误的原因及解决方法
Rust FFI内存越界未触发错误的原因及无size参数的安全检查方案
问题重现
在Rust FFI场景中,执行以下代码时,指针偏移100后访问越界内存,既未触发panic也未产生段错误,仅输出0:
Rust代码
#[repr(C)] pub struct MyStruct { pub item: u32 // 其他字段省略 } #[no_mangle] pub unsafe extern fn do_something(mut data: *mut MyStruct) { println!("{:p}", data); data = data.offset(100); println!("{:p}", data); println!("{}", (*data).item); if data.is_null() { println!("data is null"); } }
C++调用代码
#include "my_bindings.h" int main() { MyStruct *data = new MyStruct[2]; do_something(data); return 0; }
程序输出
0x55f0ba739eb0 0x55f0ba73a108 0
为什么未触发panic或段错误?
- Rust unsafe代码无自动边界检查:
do_something函数标记为unsafe,意味着主动放弃了Rust的内存安全保障,编译器不会自动插入数组边界检查逻辑,因此访问越界不会触发panic。 - 段错误的触发条件限制:段错误(SIGSEGV)是操作系统的内存保护机制,只有当访问的内存页完全不属于当前进程地址空间时才会触发。偏移100后的地址仍处于进程的堆地址范围内(堆分配通常会预留额外空间,或该地址属于之前分配后未回收的内存区域),操作系统不会拦截,此时读取到的是该内存地址的现有值(刚好为0)。
不传入size参数的前提下如何避免越界?
裸指针本身不携带任何内存块长度信息,不存在通用、跨平台且安全的方法仅通过裸指针获取内存大小,以下是几种可行的替代方案:
1. 用带长度的结构体封装指针
定义一个同时包含指针和长度的C兼容结构体,让C++端传递这个结构体而非裸指针,本质是将长度与指针绑定:
#[repr(C)] pub struct StructSlice { data: *mut MyStruct, len: usize, } #[no_mangle] pub unsafe extern fn do_something(slice: StructSlice) { if slice.len <= 100 { // 处理越界情况,比如直接返回或打印错误 return; } let data = slice.data.offset(100); println!("{}", (*data).item); }
C++端需要定义对应结构体:
struct StructSlice { MyStruct* data; size_t len; }; int main() { MyStruct* data = new MyStruct[2]; StructSlice slice = {data, 2}; do_something(slice); delete[] data; return 0; }
2. 使用哨兵值标记数组末尾
在数组最后一个元素放置一个业务场景中不可能出现的特殊值,遍历或偏移时检查该值以判断是否越界。例如,约定item字段为0xFFFFFFFF时表示数组结束:
#[no_mangle] pub unsafe extern fn do_something(mut data: *mut MyStruct) { // 遍历到哨兵值停止 for i in 0.. { if (*data).item == 0xFFFFFFFF { break; } // 处理逻辑 data = data.offset(1); } }
C++端初始化数组时需添加哨兵:
int main() { MyStruct* data = new MyStruct[3]; // 多分配一个位置放哨兵 data[0].item = 1; data[1].item = 2; data[2].item = 0xFFFFFFFF; do_something(data); delete[] data; return 0; }
注意:该方案依赖业务逻辑,若数组中可能出现哨兵值则不可用。
3. 依赖特定内存分配器的API(平台相关)
如果C++和Rust使用同一款内存分配器(如系统默认的malloc/free),可使用分配器的私有API获取内存块大小。例如GNU系统下的malloc_usable_size:
use libc::{c_void, malloc_usable_size}; #[no_mangle] pub unsafe extern fn do_something(data: *mut MyStruct) { let block_size = malloc_usable_size(data as *mut c_void); let max_elements = block_size / std::mem::size_of::<MyStruct>(); if max_elements <= 100 { // 越界处理 return; } // 安全访问 }
注意:该方案仅适用于特定平台和分配器,且无法区分new[]分配的数组和单独分配的结构体,兼容性差,不推荐作为通用方案。
内容的提问来源于stack exchange,提问作者t348575
相关产品推荐
相关产品推荐

