对于共享引用和可变引用,语义是明确的:只要您拥有对值的共享引用,其他任何东西都不能具有可变访问权限,而可变引用则不能被共享。
因此,这段代码:
#[no_mangle]
pub extern fn run_ref(a: &i32, b: &mut i32) -> (i32, i32) {
let x = *a;
*b = 1;
let y = *a;
(x, y)
}
编译(在x86_64上)的结果为:
run_ref:
movl (%rdi), %ecx
movl $1, (%rsi)
movq %rcx, %rax
shlq $32, %rax
orq %rcx, %rax
retq
请注意,内存 a 指向的位置只被读取一次,因为编译器知道对b
的写入不会修改在a
处的内存。
原始指针更加复杂。原始指针算术运算和强制类型转换是"安全"的,但对它们进行解引用则是不安全的。
我们可以将原始指针转换回共享和可变引用,然后使用它们;这肯定会涉及到通常的引用语义,并且编译器可以相应地进行优化。
但是,如果我们直接使用原始指针,那么它们的语义是什么呢?
#[no_mangle]
pub unsafe extern fn run_ptr_direct(a: *const i32, b: *mut f32) -> (i32, i32) {
let x = *a;
*b = 1.0;
let y = *a;
(x, y)
}
编译成:
run_ptr_direct:
movl (%rdi), %ecx
movl $1065353216, (%rsi)
movl (%rdi), %eax
shlq $32, %rax
orq %rcx, %rax
retq
尽管我们写入了不同类型的值,但第二次读取仍然会访问内存-似乎可以使用相同(或重叠)的内存位置调用此函数作为两个参数。换句话说,const原始指针并不禁止同时存在mut原始指针;同时拥有两个mut原始指针(可能是不同类型的)指向同一个(或重叠的)内存位置也可能是可以的。
请注意,正常的优化C/C++编译器会消除第二次读取(由于“严格别名”规则:通过不同(“不兼容”的)类型的指针修改/读取相同的内存位置在大多数情况下是未定义行为):struct tuple { int x; int y; };
extern "C" tuple run_ptr(int const* a, float* b) {
int const x = *a;
*b = 1.0;
int const y = *a;
return tuple{x, y};
}
编译成:
run_ptr:
movl (%rdi), %eax
movl $0x3f800000, (%rsi)
movq %rax, %rdx
salq $32, %rdx
orq %rdx, %rax
ret
那么,如果我们直接使用裸指针,它的语义是什么:被引用的数据是否允许重叠?
这应该直接影响编译器是否允许通过裸指针重新排序内存访问。