const 在 C/C++ 中提供什么样的优化
What kind of optimization does const offer in C/C++?
我知道,出于可读性的原因,在通过引用或指针传递参数时,您应该尽可能使用 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 的副本放入只读内存吗?
不,因为x
和someY
都生活在其范围之外,来自和/或被赋予外部世界。即使someY
在f()
本身内动态分配,它及其所有权也会交给调用方。
出现在 f(( 正文中的代码的可能优化呢?由于 const 的原因,编译器能否以某种方式改进它为 f(( 主体生成的代码?
即使调用 const 成员函数,编译器也不能假定对象x
或对象someY
的位不会更改。此外,还有其他问题(除非编译器执行全局优化(:编译器也可能不确定没有其他代码可能具有与x
和/或someY
相同的对象别名的非常量引用,以及是否在执行f();
期间偶然使用对同一对象的任何此类非常量引用,编译器甚至可能不知道是否真实对象, x
和someY
只是对它的引用,实际上首先被宣布为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
执行以下任何操作。
- 不修改对象。
- 抛弃恒常性,然后修改对象
- 修改 MyObject 的
mutable
数据成员
请注意,如果您使用声明为 const 的 MyObject 实例调用我们的函数,您将使用 #2 调用未定义的行为。
大师问题:以下行为会调用未定义的行为吗?
const int x = 1;
auto lam = [x]() mutable {const_cast<int&>(x) = 2;};
lam();
SomeClass* const pObj
创建一个指针类型的常量对象。 没有安全的方法可以更改此类对象,因此编译器可以,例如,将其缓存到只有一个内存读取的寄存器中,即使其地址被占用。
其他的不专门启用任何优化,尽管类型的const
限定符会影响重载分辨率,并可能导致选择不同且更快的函数。
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 返回的指向C++对象的链接是什么样的
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 这些是什么样的错误?即使我不在 Linux 上工作,我也遇到了 Linux 错误
- 小字符串优化(调试与发布模式)
- 浮点定向舍入和优化
- Visual Studio 调试优化如何工作?
- 为什么开关的优化方式与 c/c++ 中的链接不同?
- 线性优化目标函数中的绝对值
- GCC 会优化内联访问器吗?
- gcc 如何优化此循环?
- 什么样的 GCC 优化可能会根据是否打印来更改双精度?
- const 在 C/C++ 中提供什么样的优化