为什么不同的 C++编译器对此代码给出不同的结果?

发布于 2024-10-19 18:53:42 字数 605 浏览 9 评论 0原文

我正在编写一些 C++ 代码以供娱乐和练习,以了解有关语言功能的更多信息。我想更多地了解静态变量及其在递归函数中的行为。在 g++ 编译器中尝试此代码,得到了预期结果:

#include <iostream>
using namespace std;

int f(const int& value)
{
   static int result = 0;
   return result += value;
}

int main()
{
   cout << f(10) << ", " << f(f(10)) << ", " << f(f(f(10)));
   return 0;
}

但我的朋友在 Microsoft Visual C++ 6 中测试了相同的代码。输出为 50, 80, 90 我用其他 C++ 编译器(g++、 Borland、Code::blocks 和 Linux、Win 和 Mac 下的 MingW)输出为 110, 100, 40。我不明白输出怎么会是 50, 80, 90 ...

为什么 MSVC 的输出不同?

I'm writing some C++ codes for fun and practice, to learn more about language features. I want to know more about static variables and their behaviour in recursive functions. Trying this code in g++ compiler, I'm given expected result:

#include <iostream>
using namespace std;

int f(const int& value)
{
   static int result = 0;
   return result += value;
}

int main()
{
   cout << f(10) << ", " << f(f(10)) << ", " << f(f(f(10)));
   return 0;
}

But my friend tested same code in Microsoft Visual C++ 6. output is 50, 80, 90 I tested it with other C++ compilers (g++, Borland, Code::blocks and MingW under Linux, Win and Mac) output was 110, 100, 40. I can't understand how output could be 50, 80, 90 ...

Why MSVC's output is different?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

往事随风而去 2024-10-26 18:53:42

以下三个子表达式的计算顺序未指定:

f(10)
f(f(10))
f(f(f(10)))

编译器可以按任何顺序计算这些子表达式。您不应依赖程序中特定的求值顺序,特别是如果您打算使用多个编译器进行编译。

这是因为该表达式中的任何位置都没有序列点。唯一的要求是在需要结果之前(即在打印结果之前)对每个子表达式进行计算。

在您的示例中,实际上有几个子表达式,我在这里将其标记为 a 到 k:

//   a  b     c       d  e f      g       h  i j k
cout << f(10) << ", " << f(f(10)) << ", " << f(f(f(10)));

operator<< (a, c< /code>、dgh)都必须按顺序求值,因为每个都取决于前一个调用的结果。同样,b 必须在 a 计算之前计算,并且 k 必须在 j 之前计算,可以计算 ih

但是,其中一些子表达式之间不存在依赖关系:b 的结果不依赖于 k 的结果,因此编译器可以自由生成计算 b 的代码。代码>k然后bb然后k

有关序列点和相关未指定和未定义行为的更多信息,请考虑阅读 Stack Overflow C++ 常见问题解答文章 “未定义的行为和序列点”(您的程序没有任何未定义的行为,但本文的大部分内容仍然适用)。

The order of evaluation of the following three subexpressions is unspecified:

f(10)
f(f(10))
f(f(f(10)))

The compiler may evaluate those subexpressions in any order. You should not rely on a particular order of evaluation in your program, especially if you intend to compile using multiple compilers.

This is because there is no sequence point anywhere in that expression. The only requirement is that each of those subexpressions is evaluated before the result is needed (that is, before the result is to be printed).

In your example, there are actually several subexpressions, which I've labelled as a through k here:

//   a  b     c       d  e f      g       h  i j k
cout << f(10) << ", " << f(f(10)) << ", " << f(f(f(10)));

The calls to operator<< (a, c, d, g, and h) all have to be evaluated in order because each depends on the result of the previous call. Likewise, b has to be evaluated before a can be evaluated, and k has to be evaluated before j, i, or h can be evaluated.

However, there are no dependencies between some of these subexpressions: the result of b is not dependent upon the result of k, so the compiler is free to generate code that evaluates k then b or b then k.

For more information on sequence points and related unspecified and undefined behavior, consider reading the Stack Overflow C++ FAQ article, "Undefined Behavior and Sequence Points" (your program doesn't have any undefined behavior, but much of the article still applies).

假面具 2024-10-26 18:53:42

仅仅因为输出在屏幕上从左到右显示并不意味着计算顺序遵循相同的方向。在 C++ 中,函数参数的求值顺序未指定。另外,通过 << 运算符打印数据只是调用函数的奇特语法。

简而言之,如果您说operator<<(foo(), bar()),编译器可以先调用foobar。这就是为什么调用具有副作用的函数并将其用作其他函数的参数通常是一个坏主意。

Just because the output appears left-to-right on the screen does not mean the order of evaluation follows the same direction. In C++, the order of evaluation of function arguments is unspecified. Plus, printing data via the << operator is just fancy syntax for calling functions.

In short, if you say operator<<(foo(), bar()), the compiler can call foo or bar first. That's why it's generally a bad idea to call functions with side effects and use those as arguments to other functions.

末が日狂欢 2024-10-26 18:53:42

准确了解它在做什么的简单方法:

int f(const int& value, int fID)
{
   static int result = 0;
   static int fCounter = 0;
   fCounter++;
   cout << fCounter << ".  ID:" << fID << endl;    
   return result += value;
}

int main()
{
   cout << f(10, 6) << ", " << f(f(10, 4), 5) << ", " << f(f(f(10, 1),2),3);
   return 0;
}

我同意其他人在答案中所说的内容,但这将使您能够准确了解它在做什么。 :)

An easy way to see exactly what it is doing:

int f(const int& value, int fID)
{
   static int result = 0;
   static int fCounter = 0;
   fCounter++;
   cout << fCounter << ".  ID:" << fID << endl;    
   return result += value;
}

int main()
{
   cout << f(10, 6) << ", " << f(f(10, 4), 5) << ", " << f(f(f(10, 1),2),3);
   return 0;
}

I agree with what others have said in their answers, but this would allow you to see exactly what it is doing. :)

浮华 2024-10-26 18:53:42

前缀运算符语法被翻译为以下前缀表示法:

<<( <<( <<( cout, f(10) ), f(f(10)) ), f(f(f(10))) )
 A   B   C

现在有三个不同的函数调用,分别标识为上面的 A、B 和 C。每个调用的参数为:

     arg1        arg2
A: result of B, f(10)
B: result of C, f(f(10))
C: cout       , f(f(f(10)))

对于每个调用,编译器可以以任何顺序计算参数,为了正确计算 A 的第一个参数,必须首先计算 B,对于第一个参数也是如此B 的参数,必须评估整个 C 表达式。这意味着第一个参数依赖项要求 A、B 和 C 的执行存在偏序。每个调用和两个参数的计算也有部分排序,因此 B1 和 B2 (指调用 B 的第一个和第二个参数) 这些部分顺序不会导致

对执行调用的唯一要求,因为编译器可以决定在尝试计算第一个参数之前执行所有第二个参数,从而导致等效路径:

tmp1 = f(10); tmp2 = f(f(10)); tmp3 = f(f(f(10)));
cout << tmp1 << tmp2 << tmp3;

or

tmp3 = f(f(f(10))); tmp2 = f(f(10)); tmp1 = f(10);
cout << tmp1 << tmp2 << tmp3;

or

tmp2 = f(f(10)); tmp1 = f(10); tmp3 = f(f(f(10)));
cout << tmp1 << tmp2 << tmp3;

or ...继续组合。

The prefix operator syntax is translated into the following prefix notation:

<<( <<( <<( cout, f(10) ), f(f(10)) ), f(f(f(10))) )
 A   B   C

Now there are three different function calls, identified as A, B and C above. with the arguments of each call being:

     arg1        arg2
A: result of B, f(10)
B: result of C, f(f(10))
C: cout       , f(f(f(10)))

For each one of the calls, the compiler is allowed to evaluate the arguments in any order, for the correct evaluation of the first argument of A, B has to be evaluated first, and similarly for the first argument of B, the whole C expression has to be evaluated. This implies that there is a partial order on the execution of A, B, and C required by the first argument dependency. There is also a partial ordering on the evaluation of each call and both arguments, so B1 and B2 (referring to the first and second arguments of the call B) have to be evaluated before B.

Those partial orderings do not lead to a unique requirement for the execution of the calls, since the compiler can decide to execute all second arguments before trying to evaluate the first argument, leading to the equivalent path:

tmp1 = f(10); tmp2 = f(f(10)); tmp3 = f(f(f(10)));
cout << tmp1 << tmp2 << tmp3;

or

tmp3 = f(f(f(10))); tmp2 = f(f(10)); tmp1 = f(10);
cout << tmp1 << tmp2 << tmp3;

or

tmp2 = f(f(10)); tmp1 = f(10); tmp3 = f(f(f(10)));
cout << tmp1 << tmp2 << tmp3;

or ... keep combining.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文