Cout vs printf---缓存与引用,流处理顺序(转ithzhang,知乎郝译钧)
一个知乎问题:
VS2013,debug模式
int i = 4;
cout << i++ << i++ << endl;
输出结果:54
int i = 4;
cout << ++i << ++i << endl;
结果输出:66
同样类似的问题出现在如下函数中
void myFun(int i1, int i2) {
cout << i1 << i2;
}
从右往左的结合性我理解。。但是为什么一个是54,一个是66?
满地打滚求解释。。。为何是这样的结果。。
答案:
int i = 4;
cout << i++ << i++ << endl;
-----------------------------------------------
把++视为一个函数就好理解了
i++ 相当于
{
int var = i;
i自加1; //i为引用
return var;
}
++i 相当于
{
i自加1; //同上 i为引用
return i;
}
所以输出 54;
后边输出 66;
/ << 本身也是移位运算符,不过这个和本问题基本无关
int i = 4;
// i++,自加滞后
cout << i++ << i++ << endl;
输出结果:54
int i = 4;
// ++i,自加在前
cout << ++i << ++i << endl;
结果输出:66
为了你看的明白点,以下上代码:
int main()
{
int i;
i = 4;
cout << ++i << endl;
i = 4;
cout << i++ << endl;
int j;
i = 4;
j =4;
cout << ++j << ++i << endl;
i = 4;
j =4;
cout << j++ << i++ << endl;
i = 4;
cout << ++i <<"," << i <<"," << ++i << endl;
i = 4;
cout << i++ <<"," << i <<"," << i++ << endl;
system("pause");
return 0;
}
看输出结果:看见没…最后两行
中间的值,最终都是6
然而,对于++i,自加的运算在缓冲区输出以前,就做完了。
而对于i++,输出一步,计算一步。
至于为什么是5,6,4不是4,5,6。
1.cout输出的是缓冲区
2.C/C++的参数执行并不是从左到右的
所以,i的值经过计算会变成6,而计算的顺序是从右到左,结果都记录在输出缓存里。So,5,6,4
众所周知,cout和buffer都是有缓冲的(网上很多把cout和printf混用出错归结为一个有缓冲,一个无缓冲,事实会在下面说明)
cout和printf的输出是先从右往左读入缓冲区,再从top到bottem输出
对,这里的缓冲区相当于堆 栈的效果
a = 1; b = 2; c = 3;
cout<<a<<b<<c<<endl;
buffer:|3|2|1|<- (take “<-” as a poniter)
output:|3|2|<- (output 1) |3|<- (output 2) |<- (output 3)
然后我试了试下面的code:
#include <iostream>
using namespace std;
int c = 6;
int f()
{
c+=1;
return c;
}
int main()
{
int i = 0;
cout <<"i="<<i<<" i++="<<i++<<" i--="<<i--<<endl;
i = 0;
printf("i=%d i++=%d i--=%d\n" , i , i++ ,i-- );
cout<<f()<<" "<<f()<<" "<<f()<<endl;
c = 6;
printf("%d %d %d\n" , f() , f() ,f() );
system("pause");
return 0;
}
Under VS2005, the out put is
i=0 i++=-1 i--=0i=0 i++=-1 i--=09 8 79 8 7
But under g++( (GCC) 3.4.2 (mingw-special)), the out put is,
i=0 i++=0 i--=1i=0 i++=-1 i--=09 8 79 8 7
g++的输出有点出乎我的意料之外。原以为这是一个 bug in g++,于是去so上问了下
结果上面的牛人跟我讲,根本不是bug的问题:
The output of:
printf("i=%d i++=%d i--=%d\n" , i , i++ ,i-- );
is unspecified. This is a common pitfall of C++: argument evaluation order is unspecified.
原来在C/C++的函数里,参数的调用不是按顺序来的,这是一个陷阱。所以在不同的编译器,不同的编译时间,都有可能结果不同。
下面还有人补充:
It's worse than unspecified; it's undefined. There are no guarantees that the result will match any order of evaluation, although that's usually what happens given the obvious implementation.
但是为什么cout的输出每次都一样呢?这是偶然的吗?
当然不是了,上面的人告诉我:
Not so with the cout case: it uses chained calls (sequence points), not arguments to a single function, so evaluation order is well defined from left to right.
===========================================================================
看到这里,如果我告诉你上面关于那个“cout和printf的输出是先从右往左读入缓冲区,再从top到bottem输出”完全是瞎扯的话,你的第一反应是什么?不会吧,我的机器上也是这样的结果之类的云云?其实这是一个参数调用顺序的问题,而很不幸的是,C/C++里,关于参数调用的顺序是一个 undefined behavior,也就是说,不管什么顺序的调用都是合理的,这依赖于compiler的实现。当然,如果参数的传递是用stack来实现的话,很有可能就是上面的结果。
SO上的人告诉我:
1.
You are mixing a lot of things. To date:
Implementation details of cout
Chained calls
Calling conventions
Try to read up on them separately. And don't think about all of them in one go.
printf("i=%d i++=%d i--=%d\n" , i , i++ ,i-- );
The above line invokes undefined behavior. Read the FAQ 3.2. Note, what you observe is a side-effect of the function's calling convention and the way parameters are passed in the stack by a particular implementation (i.e. yours). This is not guaranteed to be the same if you were working on other machines.
I think you are confusing the order of function calls with buffering. When you have a cout statement followed by multiple insertions << you are actually invoking multiple function calls, one after the other. So, if you were to write:
cout << 42 << 0;
It really means: You call,
cout = operator<<(cout, 42)
and then use the return in another call to the same operator as:
cout = operator<<(cout, 0)
What you have tested by the above will not tell you anything cout's internal representation. I suggest you take a look at the header files to know more.
2.
Just as a general tip, never ever use i++ in the same line as another usage of i or i--.
The issue is that function arguments can be evaluated in any order, so if your function arguments have any side-effects (such as the increment and decrement operations) you can't guarantee that they will operate in the order you expect. This is something to avoid.
The same goes for this case, which is similar to the actual expansion of your cout usage:
function1 ( function2 ( foo ), bar );
The compiler is free to evaulate bar before calling function2, or vice versa. You can guarantee that function2 will return before function1 is called, for example, but not that their arguments are evaluated in a specific order.
This becomes a problem when you do something like:
function1 ( function2 ( i++), i );
You have no way to specify whether the "i" is evaluated before or after the "i++", so you're likely to get results that are different than you expect, or different results with different compilers or even different versions of the same compiler.
Bottom line, avoid statements with side-effects. Only use them if they're the only statement on the line or if you know you're only modifying the same variable once. (A "line" means a single statement plus semicolon.)
3.
n addition to the other answers which correctly point out that you are seeing undefined behavior, I figured I'd mention that std::cout uses an object of type std::streambuf to do its internal buffering. Basically it is an abstract class which represents of buffer (the size is particular to implementation and can even be 0 for unbufferd stream buffers). The one for std::cout is written such that when it "overflows" it is flushed into stdout.
In fact, you can change the std::streambuf associated with std::cout (or any stream for that matter). This often useful if you want to do something clever like make all std::cout calls end in a log file or something.
And as dirkgently said you are confusing calling convention with other details, they are entirely unrelated to std::cout's buffering.
对,那个输出顺序啥也不能说明,仅仅是一个参数调用的顺序而已,跟buffer一点关系都没有。
关于implementation-defined unspecified和undefined这3种行为,可以参考这个
TopLanguage上也有类似的回答:
> i = 0;
> printf("i=%d i++=%d i--=%d\n" , i , i++ ,i-- );函数参数计算顺序(不是压栈顺序)是依赖编译器的,平时这么写不是好习惯。
也有人跟我讲cout<<a<<b<<c 的调用原型其实是 operator<< ( operator<< ( operator<<
对于操作符,要看是否使用了重载。
对于c和未重载的c++操作符号,如果不是序列点运算符,其操作数的计算顺序也是编译器依赖的。如
int i=1;
(i++)<<(i++)<<(i++);
(++i)-(++i)-(++i);
两个表达式给出任何结果都是合理的,因为依赖编译器。
如果是c++重载了的操作符,它就不是“操作符”,而是函数了, 但是其优先级别和计算顺序是保留的。所以,
ostream i;
i<<a<<b<<c;
等价于 (op<<(i, a))<<b<<c
等价于 op<<((op<<(i, a), b)<<c
等价于 op<<(op<<((op<<(i, a), b), c)
但是顺然形式相等,可是a,b,c的计算顺序仍然是编译器依赖的。
因为op<<((op<<(i, a), b)可能现于c计算,(op<<(i, a))可能先于 b 计算,但怎样的结果都是合理的。
===========================================================================
然后这里说说为什么最好cout不要和printf混用的问题
下面来做一些试验(环境:g++ (GCC) 3.2.3 (mingw special 20030504-1))。#include <iostream>
using namespace std;int main() {
cout << "aaa";
printf("bbb");
return 0;
}输出为:aaabbb没有问题。如果将程序修改一下:#include <iostream>
using namespace std;int main() {
ios::sync_with_stdio(false);
cout << "aaa";
printf("bbb");
return 0;
}输出成了:bbbaaa顺序发生了错误。sync_with_stdio()是在<ios_base>中定义的,当其接受true作为参数时,将会同步iostream与 stdio中的流操作。默认是true,因此第一个程序的结果是正确的。然而,尽管C++标准中规定stdio sync标志默认是true,不同平台下的不同编译器可能并不完全支持这个标准。因此也就有了通常意义上的关于“不要混用iostream与stdio” 之类的警告。如果再修改一下程序:#include <iostream>
using namespace std;int main() {
ios::sync_with_stdio(false);
cout << "aaa" << flush;
printf("bbb");
return 0;
}
这回程序的输出就又正确了。因为flush强制清空了缓冲区,将其中的内容输出。