const 在 C/C++ 中提供什么样的优化

What kind of optimization does const offer in C/C++?

本文关键字:什么样 优化 C++ const      更新时间:2023-10-16

我知道,出于可读性的原因,在通过引用或指针传递参数时,您应该尽可能使用 const 关键字。如果我指定参数是常量,编译器是否可以进行任何优化?

可能有几种情况:

函数参数:

常量参考:

void foo(const SomeClass& obj)

常量 SomeClass 对象:

void foo(const SomeClass* pObj)

和指向 SomeClass 的常量指针:

void foo(SomeClass* const pObj)

变量声明:

const int i = 1234

函数声明:

const char* foo()

每个编译器提供什么样的编译器优化(如果有的话(?

来源

案例1:

在程序中声明const时,

int const x = 2;

编译器可以通过不为此变量提供存储来优化此const;相反,可以将其添加到符号表中。因此,后续读取只需要间接进入符号表,而不是从内存中获取值的指令。

注意:如果您执行以下操作:

const int x = 1;
const int* y = &x;

然后这将强制编译器为x分配空间。因此,对于这种情况,这种程度的优化是不可能的。

就函数参数而言const表示函数中的参数不被修改。据我所知,使用 const 没有实质性的性能提升;相反,它是确保正确性的一种手段。

<小时 />

案例2:

"将参数和/或返回值声明为 const 是否有助于编译器生成更优化的代码?">

const Y& f( const X& x )
{
    // ... do something with x and find a Y object ...
    return someY;
}

编译器可以做得更好吗?它能否避免参数或返回值的副本?

否,因为参数已经通过引用传递。

它可以将 x 或 someY 的副本放入只读内存吗?

不,因为xsomeY都生活在其范围之外,来自和/或被赋予外部世界。即使someYf()本身内动态分配,它及其所有权也会交给调用方。

出现在 f(( 正文中的代码的可能优化呢?由于 const 的原因,编译器能否以某种方式改进它为 f(( 主体生成的代码?

即使调用 const 成员函数,编译器也不能假定对象x或对象someY的位不会更改。此外,还有其他问题(除非编译器执行全局优化(:编译器也可能不确定没有其他代码可能具有与x和/或someY相同的对象别名的非常量引用,以及是否在执行f();期间偶然使用对同一对象的任何此类非常量引用,编译器甚至可能不知道是否真实对象, xsomeY只是对它的引用,实际上首先被宣布为const。

<小时 />

案例3:

void f( const Z z )
{
    // ...
}

这会有什么优化吗?

是的,因为编译器知道z确实是一个 const 对象,即使没有全局分析,它也可以执行一些有用的优化。例如,如果f()的主体包含类似 g( &z ) 的调用,编译器可以确保z的不可变部分在调用 g() 期间不会更改。

在给出任何答案之前,我想强调一下,使用或不使用const的原因实际上应该是为了程序的正确性和对其他开发人员的清晰度,而不是编译器优化;也就是说,使参数const文档,该方法不会修改该参数,并使成员函数const文档,该成员不会修改其成员的对象(至少不是以逻辑上更改任何其他 const 成员函数的输出的方式(。例如,这样做可以让开发人员避免制作不必要的对象副本(因为他们不必担心原始对象会被破坏或修改(或避免不必要的线程同步(例如,通过知道所有线程只是读取而不是改变有问题的对象(。

就编译器可以进行的优化而言,至少在理论上是这样,尽管在允许它做出某些可能破坏标准C++代码的非标准假设的优化模式下,请考虑:

for (int i = 0; i < obj.length(); ++i) {
   f(obj);
}

假设 length 函数被标记为 const,但实际上是一个昂贵的操作(假设它实际上在 O(n( 时间内运行,而不是 O(1( 时间(。如果函数f通过引用获取其参数const则编译器可能会优化此循环以:

int cached_length = obj.length();
for (int i = 0; i < cached_length; ++i) {
   f(obj);
}

。因为函数f不修改参数的事实保证了 length 函数每次在对象未更改的情况下都应返回相同的值。但是,如果声明f通过可变引用获取参数,则需要在循环的每次迭代中重新计算length,因为f可以以某种方式修改对象以产生值更改。

正如注释中指出的那样,这是假设一些额外的警告,并且只有在非标准模式下调用编译器时才有可能,该模式允许编译器做出额外的假设(例如const方法严格是其输入的函数,并且优化可以假设代码永远不会使用const_cast将 const 引用参数转换为可变引用(。

函数参数:

const对于引用的内存并不重要。这就像在优化器的背后绑一只手。

假设您调用另一个函数(例如 void bar() (中没有可见定义的foo。优化器将有一个限制,因为它无法知道bar是否修改了传递给foo的函数参数(例如,通过访问全局内存(。从外部修改内存和混叠的可能性会给该领域的优化器带来重大限制。

尽管您没有询问,但函数参数的const确实允许优化,因为优化器保证具有const对象。当然,复制该参数的成本可能远高于优化程序的好处。

请参阅:http://www.gotw.ca/gotw/081.htm


变量声明:const int i = 1234

这取决于声明的位置、创建时间和类型。此类别主要是存在const优化的地方。修改const对象或已知常量是未定义的,因此允许编译器进行一些优化;它假定您不调用未定义的行为,这引入了一些保证。

const int A(10);
foo(A);
// compiler can assume A's not been modified by foo

显然,优化器还可以识别不会更改的变量:

for (int i(0), n(10); i < n; ++i) { // << n is not const
 std::cout << i << ' ';
}

函数声明:const char* foo()

不重要。引用的存储器可以在外部修改。如果 foo 返回的引用变量可见,则优化器可以进行优化,但这与函数返回类型上是否存在const无关。

同样,const值或对象是不同的:

extern const char foo[];

const 的确切效果因使用它的每个上下文而异。如果在声明变量时使用 const,则它在物理上是 const 并有效驻留在只读内存中。

const int x = 123;

试图抛弃常量是未定义的行为:

尽管const_cast可以从任何指针或引用中删除恒常性或波动性,但使用生成的指针或引用写入声明为 const 的对象或访问声明为易失性的对象会调用未定义的行为。 CPP 首选项/const_cast

因此,在这种情况下,编译器可能会假定x的值始终123 。这打开了一些优化潜力(常量传播(

对于函数,这是另一回事。假设:

void doFancyStuff(const MyObject& o);

我们的函数doFancyStuff可以对o执行以下任何操作。

  1. 不修改对象。
  2. 抛弃恒常性,然后修改对象
  3. 修改 MyObject 的mutable数据成员

请注意,如果您使用声明为 const 的 MyObject 实例调用我们的函数,您将使用 #2 调用未定义的行为。

大师问题:以下行为会调用未定义的行为吗?

const int x = 1;
auto lam = [x]() mutable {const_cast<int&>(x) = 2;};
lam();

SomeClass* const pObj创建一个指针类型的常量对象。 没有安全的方法可以更改此类对象,因此编译器可以,例如,将其缓存到只有一个内存读取的寄存器中,即使其地址被占用。

其他的不专门启用任何优化,尽管类型的const限定符会影响重载分辨率,并可能导致选择不同且更快的函数。