以 const-reference 为参数的函数可以更改基础对象吗?

Can a function with a const-reference as its parameter change the underlying object?

提问人:Franz Ferdinand 提问时间:8/6/2023 最后编辑:Franz Ferdinand 更新时间:8/6/2023 访问量:100

问:

对非常量变量的常量引用可以转换为非常量引用,然后可以通过引用修改基础对象。 它是否也适用于函数声明中的 const-references?

也就是说,在下面的代码中,如果 'a' 最初不是 const,是否允许 func() 更改 'a'? 或者这会导致未定义的行为吗?

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 的定义在另一个翻译单元中。

C++ 引用 常量参数 传递

评论

0赞 Some programmer dude 8/6/2023
它可以尝试这样做。但这通常不是一个好主意。这破坏了该函数用户所期望的契约。如果传递了一个实际的常量对象,而这个对象确实是不允许修改的呢?
0赞 Some programmer dude 8/6/2023
所以对你来说最大的问题是:你为什么需要这样做?这个问题只是单纯的好奇心吗?如果是这样,那很好,但请编辑您的问题以告诉我们。如果你的问题背后还有其他一些潜在的实际问题,那么请直接提出这个问题,而不是把它变成一个XY问题类型的问题。
0赞 Franz Ferdinand 8/6/2023
我是出于好奇而问的,我编辑了这个问题。这将阻止编译器进行优化;在计算 func 后,它被迫再次查找“a”的值。
1赞 doug 8/6/2023
答案是肯定的。由于原始变量不是 const,因此您可以在不使用 UB 的情况下删除 const 并对其进行修改。由于它是合法代码,因此优化器不应假定该函数不会更改它。要确保它没有更改,只需按值传递即可。const_cast
0赞 Franz Ferdinand 8/6/2023
有趣。为什么选择这种语言设计将是另一个问题。

答:

10赞 Jan Schultke 8/6/2023 #1

是的,它可以。

调用将进行具有“不必要”的引用:funcconst

指向 cv 限定类型的指针或引用实际上不需要指向或引用 cv 限定的对象,但将其视为指向或引用;[...]

- [dcl.type.cv] 第3页

func可以通过以下命令删除此“不必要”:constconst_cast

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
}

在这种情况下,可以通过 进行修改,因为 不是 const 对象。但是,如果实际上是 ,那么这将是未定义的行为:funcaevilaaconst

在常量对象生存期内修改常量对象的任何尝试都会导致未定义的行为。

- [dcl.type.cv] 第4页

通常,如果给函数一个引用或指针,它可以简单地或该引用到它想要的任何类型。但是,如果访问的对象类型与引用的对象类型不相似,则在大多数情况下,这是未定义的行为。访问权限可能未定义,或本身没问题。const_castreinterpret_castconst_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

查看 Compiler Explorer 中的实时示例

此代码确实存在可以修改 .funca

  • mov dword ptr [rsp + 4], 5堆栈上的存储a
  • mov eax, dword ptr [rsp + 4]调用后堆栈中的负载afunc

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