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

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

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

  1. void func(const int& arg);
  2. int main()
  3. {
  4. int a = 5; //non-const
  5. func(a);
  6. std::cout << a; //Can func modify 'a' through a const-cast and thus output ≠5?
  7. }

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

zd287kbt

zd287kbt1#

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

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

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

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

对编译器优化的影响

考虑以下简化示例:

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


通过优化,clang输出:

  1. main:
  2. push rax
  3. mov dword ptr [rsp + 4], 5
  4. lea rdi, [rsp + 4]
  5. call func(int const&)@PLT
  6. mov eax, dword ptr [rsp + 4]
  7. pop rcx
  8. 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修改。这样更有效率。

展开查看全部

相关问题