const在C/C++中提供了哪些优化?(如果有的话)

UnT*_*aDe 58 c c++ const compiler-optimization

我知道在可能的情况下,在通过引用或指针传递参数时,应尽可能使用const关键字.如果我指定参数是常量,编译器可以做的任何优化吗?

可能有几种情况:

功能参数:

常量参考:

void foo(const SomeClass& obj)
Run Code Online (Sandbox Code Playgroud)

Constant SomeClass对象:

void foo(const SomeClass* pObj)
Run Code Online (Sandbox Code Playgroud)

并且指向SomeClass的常量指针:

void foo(SomeClass* const pObj)
Run Code Online (Sandbox Code Playgroud)

变量声明:

const int i = 1234
Run Code Online (Sandbox Code Playgroud)

函数声明:

const char* foo()
Run Code Online (Sandbox Code Playgroud)

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

rav*_*avi 30

[读者请注意,这篇文章的大部分内容都来自Herb Sutter的一篇文章 - http://www.gotw.ca/gotw/081.htm - 没有OP的归属.]

情况1:-

在程序中声明const时

int const x = 2;
Run Code Online (Sandbox Code Playgroud)

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

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

const int x = 1;
const int* y = &x;
Run Code Online (Sandbox Code Playgroud)

然后这将迫使编译器为其分配空间'x'.因此,对于这种情况,这种优化程度是不可能的.

在函数参数方面const意味着该函数中未修改参数.据我所知,使用const它并没有显着的性能提升,而是确保正确性的手段.

CASE_2: -

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

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

问题=>编译器可以做得更好吗?

=>它可以避免参数或返回值的副本吗?

不,因为参数已经通过引用传递.

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

不,因为x和y都生活在其范围之外,来自和/或被赋予外部世界.即使someY在f()本身内动态分配,它和它的所有权也会被放弃给调用者.

Ques =>在f()体内出现的代码可能优化怎么样?由于const,编译器能否以某种方式改进它为f()体生成的代码?

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

CASE_3: -

  void f( const Z z )
  {
    // ...
  }
Run Code Online (Sandbox Code Playgroud)

问题=>这会有优化吗?

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

  • 因抄袭Sutter而没有归属而被贬低:http://www.gotw.ca/gotw/081.htm (7认同)
  • 优化仍然是可能的.语言规则禁止更改`x`,因此它的值"1"仍然可以在需要*value*的任何地方替换.你是对的,需要内存来获取一个地址,但是可以跳过通过名称`x`访问该内存. (2认同)
  • @ravi你真的不需要政策,以免抄袭.只是不要相信其他人的工作,不仅仅是堆栈溢出,而是处处.这不好. (2认同)

Mic*_*yan 12

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

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

for (int i = 0; i < obj.length(); ++i) {
   f(obj);
}
Run Code Online (Sandbox Code Playgroud)

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

int cached_length = obj.length();
for (int i = 0; i < cached_length; ++i) {
   f(obj);
}
Run Code Online (Sandbox Code Playgroud)

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

正如评论中所指出的,这是假设一些额外的警告,并且只有在非标准模式下调用编译器时才有可能允许它做出额外的假设(例如,这些const方法严格地取决于它们的输入和优化可以假设代码永远不会用于const_cast将const引用参数转换为可变引用.

  • @MichaelAaronSafyan:`const`函数仍然可以在每次调用时返回不同的结果.这种优化实际上要求函数被标记为幂等的,或者编译器可以通过检查函数体来推导出该函数.参数类型没有帮助. (5认同)
  • @MichaelAaronSafyan*"但是,真的,在这种情况下它不应该被标记为const"* - 为什么不呢?`const`在方法上传达的唯一含义是它不会改变调用它时调用它的对象.**并不意味着即使对象未被更改,每次调用返回值都是相同的.方法上的`const`是一个不改变对象的承诺; 它并不意味着幂等. (4认同)
  • 这取决于动态类型的`obj`是否为`const`,是否`f`通过复制或引用获取其参数,以及`f`的主体是否在此处可见.它不取决于参考参数的`f`是否是'const`限定的. (3认同)
  • 你的新编辑(说'const`引用而不是"const参数")更清晰.现在显然是错的.你提到的转换只有在`obj`被创建`const`时才有可能,或者编译器可以看到`length()`成员函数. (3认同)
  • @MichaelAaronSafyan:如果您声称这是常用的非标准“扩展”,您最好举个例子。据我所知,没有主要的编译器实际上具有将 const_cast 转换为未定义行为的选项。 (2认同)
  • @MichaelAaronSafyan最终它与我们认为`const`对成员函数的暗示无关,也与我们认为应该使用它无关.标准是权限,而as-if规则管理所有优化.如果编译器可以证明`const`方法是幂等的,那么它可以在不修改对象的代码块中忽略调用.但是,如果它无法证明这一点并且无论如何都会进行优化,那么as-if规则就会被破坏,它实际上并不是一个C++编译器,只是一个可以解析C++代码但却错误地解释它的编译器. (2认同)

jus*_*tin 6

功能参数:

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
Run Code Online (Sandbox Code Playgroud)

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

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

函数声明: const char* foo()

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

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

extern const char foo[];


Ste*_*fan 5

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

const int x = 123;
Run Code Online (Sandbox Code Playgroud)

试图摆脱常量是未定义的行为:

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

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

对于函数来说则是另一回事。认为:

void doFancyStuff(const MyObject& o);
Run Code Online (Sandbox Code Playgroud)

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

  1. 不修改对象。
  2. 抛弃常量,然后修改对象
  3. 修改mutableMyObject 的数据成员

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

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

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