Rust FFI 和别名:C -> Rust -> C -> Rust 调用堆栈,C 值被别名:未定义的行为?
Rust FFI and Aliasing: C -> Rust -> C -> Rust call stacks with C values being aliased: Undefined Behaviour?
在以下情况下,我正在努力思考 Rust 的别名规则:
假设我们在 C 中有一个内存分配。我们将指向该分配的指针传递给 Rust。 Rust 函数对分配做一些事情,然后回调到 C 代码( 没有任何参数),其中另一个 Rust 函数使用与参数相同的分配被调用。现在,让我们假设只有第一个 Rust 函数获得可变引用。
调用堆栈如下所示:
Some C Code (owns data)
Rust(pointer as &mut)
Some C code (does not get parameters from Rust)
Rust(pointer as &)
作为一个简短的例子,我们假设有以下两个文件:
test.c
#include <stdio.h>
#include <stdlib.h>
void first_rust_function(int * ints);
void another_rust_function(const int * ints);
int * arr;
void called_from_rust() {
another_rust_function(arr);
}
int main(int argc, char ** argv) {
arr = malloc(3*sizeof(int));
arr[0]=3;
arr[1]=4;
arr[2]=53;
first_rust_function(arr);
free(arr);
}
test.rs
use std::os::raw::c_int;
extern "C" { fn called_from_rust(); }
#[no_mangle]
pub extern "C" fn first_rust_function(ints : &mut [c_int;3]) {
ints[1] = 7;
unsafe { called_from_rust() };
}
#[no_mangle]
pub extern "C" fn another_rust_function(ints : &[c_int;3]) {
println!("Second value: {}", ints[1])
}
(为了完整起见:运行 此代码打印“第二个值:7”)
请注意,从 Rust (called_from_rust()
) 回调到 C 没有任何参数。因此,Rust 编译器没有任何人可能从指向的值中读取的信息。
我的直觉告诉我这是未定义的行为,但我不确定。
我快速浏览了 Stacked Borrows,发现违反了该模型。在上面的示例中,只有 Rule (protector)
被破坏了,但是如果 first_rust_function(ints : &mut [c_int;3])
在调用 called_from_rust()
之后仍然使用 ints
,那么也会违反其他规则。
但是,我还没有找到任何官方文档说明 Stacked Borrows 是 Rust 编译器使用的别名模型,并且在 Stacked Borrows 下被认为未定义的所有内容实际上在 Rust 中都是未定义的。天真地,这看起来与将 &mut
强制转换为 &
非常相似,所以它实际上可能是理智的,但鉴于 called_from_rust()
没有将引用作为参数,我不认为这个推理适用。
这让我想到了实际问题:
- 上面的代码是否调用了未定义的行为(why/why 不是?)
- 如果未定义:如果
called_from_rust()
将指针作为参数并将其向前传递,行为是否定义明确:void called_from_rust(const int * i) { another_rust_function(i); }
?
- 如果两个 Rust 函数都使用
&mut [c_int;3]
怎么办?
Is the above code invoking undefined behaviour?
是的,您违反了 Rust 的指针别名规则。依赖 Stacked Borrows 规则有点可疑,因为正如您所暗示的,我认为它没有被正式采用为 Rust 的内存访问模型(即使它只是对当前语义的形式化)。然而,是一个实用而具体的规则是 LLVM 的 noalias
属性,Rust 编译器将其用于 &mut
个参数。
This indicates that memory locations accessed via pointer values based on the argument or return value are not also accessed, during the execution of the function, via pointer values not based on the argument or return value. ...
因此,由于您根据 [=16= 中的 ints
从 而不是 的指针访问 another_rust_function
中的 ints[1]
] 在该功能的执行过程中,这是一种违规行为。鉴于这种未定义的行为,我相信编译器完全有权让代码打印“第二个值:4”。
Would the behaviour be well-defined if called_from_rust()
would have the pointer as parameter and pass it forward: void called_from_rust(const int * i) { another_rust_function(i); }
?
是的,这将使它定义明确。您可以看到,因为 Rust 借用检查器可以看到该值可以在 called_from_rust()
中使用,并且会防止在该调用周围不正确地使用 ints
。
What if both Rust functions were using &mut [c_int;3]
?
如果您使用上面的修复方法,其中第二个借用是基于第一个,那么就没有问题。如果你不这样做,那就更糟了。
在以下情况下,我正在努力思考 Rust 的别名规则:
假设我们在 C 中有一个内存分配。我们将指向该分配的指针传递给 Rust。 Rust 函数对分配做一些事情,然后回调到 C 代码( 没有任何参数),其中另一个 Rust 函数使用与参数相同的分配被调用。现在,让我们假设只有第一个 Rust 函数获得可变引用。
调用堆栈如下所示:
Some C Code (owns data)
Rust(pointer as &mut)
Some C code (does not get parameters from Rust)
Rust(pointer as &)
作为一个简短的例子,我们假设有以下两个文件: test.c
#include <stdio.h>
#include <stdlib.h>
void first_rust_function(int * ints);
void another_rust_function(const int * ints);
int * arr;
void called_from_rust() {
another_rust_function(arr);
}
int main(int argc, char ** argv) {
arr = malloc(3*sizeof(int));
arr[0]=3;
arr[1]=4;
arr[2]=53;
first_rust_function(arr);
free(arr);
}
test.rs
use std::os::raw::c_int;
extern "C" { fn called_from_rust(); }
#[no_mangle]
pub extern "C" fn first_rust_function(ints : &mut [c_int;3]) {
ints[1] = 7;
unsafe { called_from_rust() };
}
#[no_mangle]
pub extern "C" fn another_rust_function(ints : &[c_int;3]) {
println!("Second value: {}", ints[1])
}
(为了完整起见:运行 此代码打印“第二个值:7”)
请注意,从 Rust (called_from_rust()
) 回调到 C 没有任何参数。因此,Rust 编译器没有任何人可能从指向的值中读取的信息。
我的直觉告诉我这是未定义的行为,但我不确定。
我快速浏览了 Stacked Borrows,发现违反了该模型。在上面的示例中,只有 Rule (protector)
被破坏了,但是如果 first_rust_function(ints : &mut [c_int;3])
在调用 called_from_rust()
之后仍然使用 ints
,那么也会违反其他规则。
但是,我还没有找到任何官方文档说明 Stacked Borrows 是 Rust 编译器使用的别名模型,并且在 Stacked Borrows 下被认为未定义的所有内容实际上在 Rust 中都是未定义的。天真地,这看起来与将 &mut
强制转换为 &
非常相似,所以它实际上可能是理智的,但鉴于 called_from_rust()
没有将引用作为参数,我不认为这个推理适用。
这让我想到了实际问题:
- 上面的代码是否调用了未定义的行为(why/why 不是?)
- 如果未定义:如果
called_from_rust()
将指针作为参数并将其向前传递,行为是否定义明确:void called_from_rust(const int * i) { another_rust_function(i); }
? - 如果两个 Rust 函数都使用
&mut [c_int;3]
怎么办?
Is the above code invoking undefined behaviour?
是的,您违反了 Rust 的指针别名规则。依赖 Stacked Borrows 规则有点可疑,因为正如您所暗示的,我认为它没有被正式采用为 Rust 的内存访问模型(即使它只是对当前语义的形式化)。然而,是一个实用而具体的规则是 LLVM 的 noalias
属性,Rust 编译器将其用于 &mut
个参数。
This indicates that memory locations accessed via pointer values based on the argument or return value are not also accessed, during the execution of the function, via pointer values not based on the argument or return value. ...
因此,由于您根据 [=16= 中的 ints
从 而不是 的指针访问 another_rust_function
中的 ints[1]
] 在该功能的执行过程中,这是一种违规行为。鉴于这种未定义的行为,我相信编译器完全有权让代码打印“第二个值:4”。
Would the behaviour be well-defined if
called_from_rust()
would have the pointer as parameter and pass it forward:void called_from_rust(const int * i) { another_rust_function(i); }
?
是的,这将使它定义明确。您可以看到,因为 Rust 借用检查器可以看到该值可以在 called_from_rust()
中使用,并且会防止在该调用周围不正确地使用 ints
。
What if both Rust functions were using
&mut [c_int;3]
?
如果您使用上面的修复方法,其中第二个借用是基于第一个,那么就没有问题。如果你不这样做,那就更糟了。