c++ 一个以const-reference作为参数的函数可以改变底层对象吗?

5cnsuln7  于 2023-08-09  发布在  其他
关注(0)|答案(1)|浏览(122)

对非常量变量的常量引用可以转换为非常量引用,然后可以通过引用修改基础对象。它是否也适用于函数声明中的常量引用?
也就是说,在下面的代码中,func()是否允许更改'a',前提是'a'最初不是const?这是否会导致未定义的行为?

void func(const int& arg);

int main()
{
   int a = 5; //non-const
   func(a);
   std::cout << a; //Can func modify 'a' through a const-cast and thus output ≠5?
}

字符串
我问这个问题是因为这会阻止编译器进行优化,因为它会在func求值后被迫再次查找'a'的值;特别是如果func的定义在另一个翻译单元中。

zd287kbt

zd287kbt1#

是的,可以。
调用func将创建一个引用,该引用具有“不必要的”const
对cv限定类型的指针或引用不需要实际指向或引用cv限定的对象,但会被当作是这样;[...]

  • [dcl.type.cv]第3页
    func可以用const_cast删除这个“不必要的”const
void func(const int& arg)
{
    int& evil = const_cast<int&>(arg); // OK so far
    evil = 123; // undefined behavior only if arg refers to a const object
}

字符串
在本例中,func * 可以 * 修改aevil,因为a不是const对象。但是,如果a实际上是const,那么这将是未定义的行为:
在const对象的生存期内,任何修改const对象的尝试都会导致未定义的行为。

  • [dcl.type.cv]第4页
    一般来说,如果给一个函数一个引用或指针,它可以简单地const_castreinterpret_cast引用它想要的任何类型。但是,如果被访问的对象类型与引用的类型不相似,则在大多数情况下这是未定义的行为。访问可能未定义const_castreinterpret_cast本身就可以。

对编译器优化的影响

考虑以下简化示例:

void func(const int&); // "Black box" function.
                       // The compiler cannot prove that func isn't modifying
                       // its arguments, so it must assume that this happens.
int main()
{
   int a = 5;
   func(a);  // func may be modifying a.
   return a; // The compiler cannot assume that this is 'return 5'.
}


通过优化,clang输出:

main:
        push    rax
        mov     dword ptr [rsp + 4], 5
        lea     rdi, [rsp + 4]
        call    func(int const&)@PLT
        mov     eax, dword ptr [rsp + 4]
        pop     rcx
        ret

这段代码的缺点是func可以修改a

  • mov dword ptr [rsp + 4], 5a存储在堆栈上
  • 调用func后,mov eax, dword ptr [rsp + 4]从堆栈加载a

如果改为写入const int a = 5;,则程序集以mov eax, 5结束,并且a不会溢出到堆栈上,因为它不能被func修改。这样更有效率。

相关问题