2011-09-22 49 views
7

我读过C++标准允许优化到实际上可能会妨碍预期功能的点。当我这样说时,我正在谈论返回值优化,其中您可能实际上在复制构造函数中有一些逻辑,但编译器会优化该调用。关于C++优化的问题

我觉得这有些不好,因为在不知道这个的人可能会花相当多的时间修复由此产生的错误。

我想知道的是,是否有任何其他情况下编译器的过度优化可以改变功能。

例如,是这样的:

int x = 1; 
x = 1; 
x = 1; 
x = 1; 

可能被优化,以一个单一的X = 1;

假设我有:

class A; 

A a = b; 
a = b; 
a = b; 

难道这可能还可以优化?也许不是最好的例子,但我希望你知道我的意思......

+4

我不紧密票同意。这是一个真实的,可回答的问题。 –

+4

当删除副本会导致代码中存在错误时,那么您在开始时就设计了错误的副本。你的代码不应该依赖于周围有多少物体,也不应该取决于事物被复制/分配的频率。 – PlasmaHH

+1

复制ctor中的逻辑应该是用于复制对象的逻辑。如果它不被复制,那么为什么复制ctor逻辑需要运行? –

回答

12

Eliding复制操作是其中一个编译器被允许优化的地步副作用明显改变的唯一案例。不要依赖被调用的拷贝构造函数,编译器可能会优化掉这些调用。

对于其他任何事情,“as-if”规则都适用:只要可见副作用与编译器根本没有进行过优化,编译器可能会进行优化。

(“可见副作用”包括,例如,写入到控制台或文件系统,而不是东西运行时间和CPU风扇的转速。)

+0

+1:“副作用明显改变。”可能值得扩展的是什么是可见的副作用。 –

+1

我会注意到这是在r值被引入之前。这允许优化。既然r值存在,那些优化可能不是相关的(复制elision仍然比调用移动构造函数的速度快,但不是相同的边距),但行为被保留,但不是为了向后兼容,而是因为它仍然提供了一个好处,人们已经学会了不使用复制构造函数的技巧。 –

+0

@Rob:这对你的品味是否足够?) – sbi

1

这将取决于如何class A实施,是否编译可以看到实施和它是否足够聪明。例如,如果operator=()class A有一些副作用,这种优化会改变程序行为,并且是不可能的。

3

它可能会被优化,是的。但仍具有对过程中的一些控制,例如,假设代码:

int x = 1; 
x = 1; 
x = 1; 
x = 1; 
volatile int y = 1; 
y = 1; 
y = 1; 
y = 1; 

只要既不的x,或y被用于该片段的下方,VS 2010生成代码:

 
    int x = 1; 
    x = 1; 
    x = 1; 
    x = 1; 
    volatile int y = 1; 
010B1004 xor   eax,eax 
010B1006 inc   eax 
010B1007 mov   dword ptr [y],eax 
    y = 1; 
010B100A mov   dword ptr [y],eax 
    y = 1; 
010B100D mov   dword ptr [y],eax 
    y = 1; 
010B1010 mov   dword ptr [y],eax 

即,优化将所有行用“x”去掉,并将所有四行用“y”去掉。这就是易失性的工作原理,但重点是你仍然可以控制编译器为你做什么。

无论是类还是原始类型 - 都取决于编译器,它的优化上限有多复杂。

学习另一个代码段:

class A 
{ 
private: 
    int c; 

public: 
    A(int b) 
    { 
     *this = b; 
    } 
    A& operator = (int b) 
    { 
     c = b; 
     return *this; 
    } 
}; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    int b = 0; 
    A a = b; 
    a = b; 
    a = b; 
    return 0; 
} 

Visual Studio 2010的优化条所有代码什么都没有,在发布版本与“全面优化” _tmain不只是任何操作并立即返回零。

-1

我不知道C++的那么多,但我目前正在读编译器,原理,技术和工具

这里是他们的部分片段上的代码优化:

机器无关的代码优化阶段尝试改进 中间代码,以便产生更好的目标代码。通常 更好意味着更快,但可能需要其他目标,例如更短的代码或消耗更少功率的目标代码。例如直接算法使用来自语义分析器的来自 的树表示中的每个运算符的指令,生成中间码(1.3)。一个简单的中间代码生成 算法跟着代码优化是合理的方式,以 生成好的目标代码。优化可以减少从整数到浮点的60次转换可以在编译时完成一次,并且 ,所以通过用浮点数60.0替换整数6-,可以消除错误操作 。 此外T3只使用一次trasmit其值设置为ID1所以 优化器可以将1.3到短序列(1.4)

1.3 
t1 - intoffloat(60 
t2 -- id3 * id1 
ts -- id2 + t2 
id1 t3 

1.4 
t1=id3 * 60.0 
id1 = id2 + t1 

人人,我的意思是说,代码优化应该付出了更深层次,因为代码处于如此简单的状态不会影响你的代码的作用

0

优化不(以适当的术语)“删除调用复制或分配”。 它将另一个有限状态的有限状态机转换成具有相同外部行为的机器。

现在,如果你repeadly调用

a=b; a=b; a=b; 

编译器做什么取决于什么operator=实际上是。 如果编译器发现调用没有机会改变程序的状态(并且“程序的状态”为“所有内容的生命周期都超过了作用域可以访问的范围”),它将剥离它。 如果这不能被“证明”,通话将保持原状。

无论编译器会做什么,不要太担心:编译器不能(通过合约)更改程序或其一部分的外部逻辑。

-1

我在const变量和const_cast上遇到了一些麻烦。当编译器被用来计算其他东西时,编译器会产生不正确的结果。 const变量被优化掉了,它的旧值被编译为编译时常量。真正的“意外行为”。好吧,也许不是;)

例子:

const int x = 2; 
const_cast<int&>(x) = 3; 
int y = x * 2; 
cout << y << endl; 
+0

对此没有任何意外。 'const_cast'只是用于抛出引用或指针的const属性,或者不被声明为const的引用对象(例如,当处理错误的API或者定义一对'operator'调用的快捷方式时)。相比之下,标准非常故意指出,抛弃最初声明为“const”的变量的'const'是未定义的行为。 –