为什么 C 编译器不能优化更改常量指针的值,假设指向同一变量的两个指针是非法的/UB?



最近我偶然发现了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函数无法优化第一行,因为ab可能指向相同的数字。在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指针访问变量。

如果ab指向同一个对象,则通过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编译器不知道可能传递给它的完整离散地址集,因为直到链接时间/运行时才知道,因为函数可以从多个翻译单元调用,因此它会考虑处理ab可能指向的任何合法地址,当然也包括它们重叠的情况。

因此,您需要使用restrict来告诉它,更新a(函数允许这样做,因为它不是指向const的指针,但即使这样,函数也可以丢弃const)不会更新b所指向的值,该值需要包含在比较中,以使其为0,因此需要继续进行比较之前发生的对a的存储,而在rust中,默认假设是受限的。然而,函数的编译器知道*a*(a+1-1)相同,因此不会产生两个单独的存储,但不知道a还是b重叠。

相关内容

  • 没有找到相关文章

最新更新