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

izl*_*lin 62 c strict-aliasing undefined-behavior rust

最近我偶然发现了 Rust 和 C 之间的比较,它们使用以下代码:

bool f(int* a, const int* b) {
  *a = 2;
  int ret = *b;
  *a = 3;
  return ret != 0;
}
Run Code Online (Sandbox Code Playgroud)

在 Rust(相同的代码,但使用 Rust 语法)中,它生成以下汇编代码:

    cmp      dword ptr [rsi], 0 
    mov      dword ptr [rdi], 3 
    setne al                    
    ret
Run Code Online (Sandbox Code Playgroud)

使用 gcc 时,它会产生以下结果:

   mov      DWORD PTR [rdi], 2   
   mov      eax, DWORD PTR [rsi]
   mov      DWORD PTR [rdi], 3        
   test     eax, eax                  
   setne al                           
   ret
Run Code Online (Sandbox Code Playgroud)

文本声称 C 函数不能优化第一行,因为ab可能指向相同的数字。在 Rust 中这是不允许的,因此编译器可以将其优化掉。

现在我的问题:

该函数采用一个const int*其为一个指向const int的。我读了这个问题,它指出用指针修改 const int 应该导致编译器警告和 UB 中最糟糕的强制转换。

如果我用两个指向同一个整数的指针调用它,这个函数会导致 UB 吗?

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

链接至神箭

Mor*_*sen 59

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

因为您没有指示 C 编译器这样做 - 允许做出这种假设。

C 有一个类型限定符,正好用于 this 调用restrict,大致意思是:this 指针不与其他指针重叠(不完全是,而是一起玩)。

汇编输出为

bool f(int* restrict a, const int* b) {
  *a = 2;
  int ret = *b;
  *a = 3;
  return ret != 0;
}
Run Code Online (Sandbox Code Playgroud)

        mov     eax, DWORD PTR [rsi]
        mov     DWORD PTR [rdi], 3
        test    eax, eax
        setne   al
        ret
Run Code Online (Sandbox Code Playgroud)

...删除了分配 *a = 2

来自https://en.wikipedia.org/wiki/Restrict

在 C 编程语言中,restrict 是一个可以在指针声明中使用的关键字。通过添加这个类型限定符,程序员向编译器提示,在指针的生命周期内,只有指针本身或直接从它派生的值(例如指针 + 1)将用于访问它指向的对象。

  • 严格来说,所有 Rust 引用都是“restrict”,甚至是共享引用——因此 Aiden4 的 Rust 代码的等价物是“bool f(int * limit a, const int* limit b)”。维基百科省略了关于“restrict”的两个事实,这些事实使这项工作得以实现:首先,单指针访问规则仅适用*如果指针后面的对象被修改*(因此“b”既可以是“restrict”也可以是别名,因为`*b` 未修改);其次,“const”限定的“restrict”指针*不得*指向被修改的内容(与普通的“const”指针不同)。(C99 6.7.3.1p4) (11认同)
  • @trentcl:“constrestrict”指针可以指向被修改的对象,如果它不用于访问对象的任何修改部分。例如,给定“void test(int *restrict p, int const *restrict q)”,代码可以写入“p[0]”并读取“p[1]”和“q[1]”,前提是它永远不会读取 `q[0]`,也不会(对于 clang 或 gcc)测试任何限制限定指针与不是从它派生的任何内容的相等性。 (4认同)

pmg*_*pmg 50

该函数int f(int *a, const int *b);承诺不会b 通过该指针更改内容......它没有承诺通过a指针访问变量。

如果ab指向同一个对象,改变它通过a是合法的(提供的底层对象是可修改的,当然)。

例子:

int val = 0;
f(&val, &val);
Run Code Online (Sandbox Code Playgroud)

  • 值得注意的是,通过“b”改变“*b”(在抛弃“const”之后)也将是合法的C。“const”本质上是程序员的一个lint,而不是编译器的提示——C不是允许通过假设它不改变“*b”来优化对“f”的调用。`restrict` 限定的指针是另一回事(参见 Morten Jensen 的回答,以及我的评论)。 (8认同)
  • @trentcl:不确定你的意思是“提示”还是实际上的“lint”,但无论哪种方式都有效。 (4认同)

Aid*_*en4 25

虽然其他答案提到了 C 方面,但仍然值得一看 Rust 方面。使用 Rust,你的代码可能是这样的:

fn f(a:&mut i32, b:&i32)->bool{
    *a = 2;
    let ret = *b;
    *a = 3;
    return ret != 0;
}
Run Code Online (Sandbox Code Playgroud)

该函数接受两个引用,一个可变,一个不可变。引用是保证对读取有效的指针,并且可变引用也保证是唯一的,因此它被优化为

        cmp     dword ptr [rsi], 0
        mov     dword ptr [rdi], 3
        setne   al
        ret
Run Code Online (Sandbox Code Playgroud)

然而,Rust 也有与 C 的指针等价的原始指针,并且不做这样的保证。以下函数接受原始指针:

unsafe fn g(a:*mut i32, b:*const i32)->bool{
    *a = 2;
    let ret = *b;
    *a = 3;
    return ret != 0;
}
Run Code Online (Sandbox Code Playgroud)

错过了优化并编译为:

        mov     dword ptr [rdi], 2
        cmp     dword ptr [rsi], 0
        mov     dword ptr [rdi], 3
        setne   al
        ret
Run Code Online (Sandbox Code Playgroud)

神弩链接

  • 所以他们正在将苹果与橙子进行比较。好一个!+1:D (5认同)