过早的优化还是我疯了?

7

我最近在comp.lang.c++版块看到一段代码,它返回了一个静态整数的引用。代码大致如下:

int& f()
{
   static int x;
   x++;
   return x;
}

int main()
{
  f()+=1; //A
  f()=f()+1; //B
  std::cout<<f();

}

当我使用我的酷炫Visual Studio调试器调试应用程序时,我只看到了一次对语句A的调用,你猜我有多震惊。我一直认为i+=1等同于i=i+1,所以f()+=1应该等同于f()=f()+1,我应该会看到两次对f()的调用,但我只看到了一次。这是怎么回事?我是疯了还是我的调试器出问题了,还是这是过早优化的结果?

4
static int x 未初始化。 - Kirill V. Lyadvinsky
1
那与问题无关。 - BjoernD
1
@Rambo:我认为你在谈论这个主题:http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/566222d9b756ecc5/3279970278f46946 - Prasoon Saurav
8
静态变量如果您没有指定其他值,则会被初始化为零。实际上,要精确些,它们总是被初始化为零,只有当您指定另一个值时,才会被初始化为其他值(这是同一变量可以合法地被初始化两次为两个不同值的少见情况之一)。 - Jerry Coffin
1
就术语而言,“过早优化”必然是判断错误,并且仅适用于程序员进行的优化(试图优化对程序速度没有重大影响的内容,并在此过程中使事情难以阅读或维护)。编译器的优化对源代码没有影响,因此要么正确,要么不正确(即存在漏洞)。 - Tyler McHenry
5个回答

26
这是《标准》关于“+=”及其相关表达式的说明:
5.17-7: 形如 E1 op= E2 的表达式行为等价于 E1 = E1 op E2,唯一不同之处在于 E1 只计算一次。所以编译器的处理方式是正确的。

4
不错的发现。 :-) - Martin York

10

i+=1在功能上与i=i+1是相同的。实际上,它们的实现方式不同(基本上,它被设计成利用CPU级别的优化)。

但本质上,左侧只会被评估一次。它产生一个非常量左值,这就足以读取该值、加一并将其写回。

当您为自定义类型创建重载运算符时,这更加明显。operator+=修改了this实例。operator+返回一个新实例。通常建议(在C++中)首先编写oop +=,然后再以其为基础编写op+。

(请注意,这仅适用于C++;在C#中,op+=与您所假设的完全相同:只是op+的简写,您无法创建自己的op+=。它会自动从Op+中创建出来。)


9
你的思维很有逻辑性,但并不正确。
i += 1;
// This is logically equivalent to:
i = i + 1;

但是逻辑等价和完全相同并不是一回事。
代码应该被看作是这样的:
int& x = f();
x += x;
// Now you can use logical equivalence.
int& x= f();
x = x + 1;

编译器不会自动添加函数调用,除非你在代码中明确地放置了两个函数调用。如果你的函数中有副作用(就像你一样),并且编译器开始添加难以看到的隐式调用,那么理解代码流程将变得非常困难,从而使维护工作变得非常困难。

3

f() 返回静态整数的引用。然后 += 1 将一加到这个内存位置 - 在语句 A 中不需要调用两次。


0
在我所见过的所有支持 a += 运算符的语言中,编译器会对左侧操作数求值一次以得到某种类型的地址,然后同时使用该地址来读取旧值和写入新值。+= 运算符不仅是语法糖;正如您所指出的那样,它可以实现通过其他方式难以实现的表达式语义。
顺便提一下,vb.net 和 Pascal 中的 "With" 语句都具有类似的功能。类似这样的语句:
' Assime Foo is an array of some type of structure, Bar is a function, and Boz is a variable. With Foo(Bar(Boz)) .Fnord = 9 .Quack = 10 End With
将计算 Foo(Bar(Boz)) 的地址,然后将该结构的两个字段设置为九和十。在 C 中等效于:
{ FOOTYPE *tmp = Foo(Bar(Boz)); tmp->Fnord = 9; tmp->Quack = 10; }
但是 vb.net 和 Pascal 不公开临时指针。虽然可以在 VB.net 中不使用 "With" 来保存 Bar() 的结果来实现相同的效果,但使用 "With" 可以避免使用临时变量。

显然,例如C#在评估LHS时会进行两次操作,正如James在他的回答中指出的那样。 - Georg Fritzsche

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接