最近我偶然发现了Rust和C之间的比较,它们使用了以下代码:
bool f(int* a, const int* b) {
*a = 2;
int ret = *b;
*a = 3;
return ret != 0;
}
在Rust(相同的代码,但使用Rust语法)中,它生成以下汇编程序代码:
cmp dword ptr [rsi], 0
mov dword ptr [rdi], 3
setne al
ret
在使用gcc时,它会产生以下内容:
mov DWORD PTR [rdi], 2
mov eax, DWORD PTR [rsi]
mov DWORD PTR [rdi], 3
test eax, eax
setne al
ret
文本声称C函数无法优化第一行,因为a
和b
可能指向相同的数字。在Rust中,这是不允许的,因此编译器可以对其进行优化。
现在我的问题是:
该函数采用const int*
,这是一个指向const int的指针。我读过这个问题,它指出用指针修改const int应该会导致编译器警告和UB中最糟糕的强制转换。
如果我用两个指向同一整数的指针调用这个函数,它会产生UB吗?
为什么C编译器不能优化第一行,假设指向同一变量的两个指针是非法的/UB?
链接到导螺杆
为什么C编译器不能优化第一行,假设指向同一变量的两个指针是非法的/UB?
因为您还没有指示C编译器这样做——所以它可以做出这样的假设。
C有一个名为restrict
的类型限定符,大致意思是:这个指针不与其他指针重叠(不是,而是一起播放)。
的装配输出
bool f(int* restrict a, const int* b) {
*a = 2;
int ret = *b;
*a = 3;
return ret != 0;
}
是
mov eax, DWORD PTR [rsi]
mov DWORD PTR [rdi], 3
test eax, eax
setne al
ret
去除/优化分配CCD_ 5
发件人https://en.wikipedia.org/wiki/Restrict
在C编程语言中,restrict是一个可以在指针声明中使用的关键字。通过添加此类型限定符,程序员向编译器提示,在指针的生存期内,只有指针本身或直接从中派生的值(如指针+1)将用于访问它所指向的对象。
函数int f(int *a, const int *b);
承诺不会通过该指针更改b
的内容。。。它没有承诺通过a
指针访问变量。
如果a
和b
指向同一个对象,则通过a
更改它是合法的(当然,前提是底层对象是可修改的)。
示例:
int val = 0;
f(&val, &val);
虽然其他答案提到了C端,但仍然值得一看Rust端。有了Rust,你的代码可能是这样的:
fn f(a:&mut i32, b:&i32)->bool{
*a = 2;
let ret = *b;
*a = 3;
return ret != 0;
}
该函数接受两个引用,一个是可变的,一个不是。引用是保证对读取有效的指针,可变引用也保证是唯一的,因此它被优化为
cmp dword ptr [rsi], 0
mov dword ptr [rdi], 3
setne al
ret
然而,Rust也有相当于C的指针的原始指针,并且没有这样的保证。以下函数接收原始指针:
unsafe fn g(a:*mut i32, b:*const i32)->bool{
*a = 2;
let ret = *b;
*a = 3;
return ret != 0;
}
错过了优化并编译为:
mov dword ptr [rdi], 2
cmp dword ptr [rsi], 0
mov dword ptr [rdi], 3
setne al
ret
Godbolt链接
函数接受一个
const int*
,它是指向常量内部的指针。
否,const int*
不是指向常量int的指针。任何这样说的人都是被欺骗了。
-
int*
是指向一个绝对不是常量的int的指针。 -
const int*
是指向常量未知的int的指针。 -
没有办法表达指向一个绝对是const的int的指针的概念。
如果C是一种设计得更好的语言,那么const int *
将是指向常量int的指针,mutable int *
(借用C++中的关键字)将是指向非常量int的指示器,而int *
将是指向未知常量的int的指针。删除限定符(即忘记指向的类型)是安全的——与真实C相反,添加const
限定符是安全的。我没有使用过Rust,但从另一个答案中的例子来看,它使用了这样的语法。
Bjarne Stroustrup介绍了const
,最初将其命名为readonly
,这更接近其实际含义。int readonly*
会更清楚地表明,它是只读的指针,而不是指向的对象。const
的更名让几代程序员感到困惑。
当我有选择的时候,我总是写foo const*
,而不是const foo*
,作为仅次于readonly*
的东西。
需要注意的是,这个问题是关于-Ofast
的优化,以及那里的情况如何。
本质上,函数的C编译器不知道可能传递给它的完整离散地址集,因为直到链接时间/运行时才知道,因为函数可以从多个翻译单元调用,因此它会考虑处理a
和b
可能指向的任何合法地址,当然也包括它们重叠的情况。
因此,您需要使用restrict
来告诉它,更新a
(函数允许这样做,因为它不是指向const的指针,但即使这样,函数也可以丢弃const)不会更新b
所指向的值,该值需要包含在比较中,以使其为0,因此需要继续进行比较之前发生的对a
的存储,而在rust中,默认假设是受限的。然而,函数的编译器知道*a
与*(a+1-1)
相同,因此不会产生两个单独的存储,但不知道a
还是b
重叠。