C优化:为什么编译器将对象视为常量?

Mar*_*arc 5 c assembly gcc compiler-optimization

编译以下C模块

static const int i = 1;

void f (const int *i);

int g (void)
{
  f (&i);
  return i;
}
Run Code Online (Sandbox Code Playgroud)

gcc -S -O3在x86_64机器上使用会为函数生成以下程序集g:

g:
        leaq    i(%rip), %rdi
        subq    $8, %rsp
        call    f@PLT
        movl    $1, %eax           # inlined constant as an immediate
        addq    $8, %rsp
        ret
Run Code Online (Sandbox Code Playgroud)

换句话说,return语句被编译为将常量移动$1到返回寄存器中%eax,这是有道理的,因为它i被声明为常量.


但是,如果我删除它,const那么我有

static int i = 1;

void f (const int *i);

int g (void)
{
  f (&i);
  return i;
}
Run Code Online (Sandbox Code Playgroud)

输出gcc -S -O3突然变成:

g:
        leaq    i(%rip), %rdi
        subq    $8, %rsp
        call    f@PLT
        movl    i(%rip), %eax     # reload i
        addq    $8, %rsp
        ret
Run Code Online (Sandbox Code Playgroud)

也就是说,在调用之后,从内存中显式加载返回值f.

为什么会这样?f声明的参数是一个指向常量int的指针,所以f不应该允许改变i.此外,f不能调用修改函数i通过非const引用,因为只有这样的功能可能是g因为i被声明为静态的.

prl*_*prl 4

只要引用的对象未声明为 const,将指向 const 的指针转换为指向非 const 的指针并修改引用的对象就不是未定义行为。

6.7.3p6 说:“如果尝试通过使用非 const 限定类型的左值来修改用 const 限定类型定义的对象,则行为是未定义的。”

  • @Marc:是的,就 ABI / 调用约定而言,它们“完全”相同。唯一的区别是编译时检查您不会通过指向“const *”的指针直接修改指向的对象。有趣的事实:在 x86-64 和 i386 System V ABI 中,`foo(const big_object by_value)` 仍然“拥有”对象被复制到的堆栈内存。因此它可以使用该堆栈空间作为临时空间,并且调用者必须假设它已被破坏并重新复制它以使用与参数相同的对象进行另一个函数调用。(因此 ABI 也不关心传递“const”对象。) (4认同)
  • 是的。不过,它在函数内部有所不同,因为声明必须匹配,并且第一个函数在没有强制转换的情况下无法通过指针写入。 (2认同)