注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

还东国的博客

行之苟有恒,久久自芬芳

 
 
 

日志

 
 

C++小知识11---[转]cout和printf的缓冲机制  

2010-08-27 14:56:23|  分类: C++(VC)编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

自己的一句话:很多东西,不要想当然,更不要看到什么就以偏盖全,要认真看书,最好多看看标准,转载此文,以记述在BLOG“内存使用的优秀习惯”中遇到的类似的问题。

[转]cout和printf的缓冲机制
已有 342 次阅读  2009-06-26 13:29

众所周知,cout和buffer都是有缓冲的(网上很多把cout和printf混用出错归结为一个有缓冲,一个无缓冲,事实会在下面说明)

cout和printf的输出是先从右往左读入缓冲区,再从top到bottem输出

对,这里的缓冲区相当于堆栈的效果

a = 1; b = 2; c = 3;
 


view plaincopy to clipboardprint?
cout<<a<<b<<c<<endl;  

cout<<a<<b<<c<<endl;
buffer:|3|2|1|<-   (take “<-” as a poniter)

 

output:|3|2|<-     (output 1)
|3|<-       (output 2)
|<-         (output 3)

然后我试了试下面的code:

 


view plaincopy to clipboardprint?
#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;   
}  

#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--=0
i=0 i++=-1 i--=0
9 8 7
9 8 7

But under g++( (GCC) 3.4.2 (mingw-special)), the out put is,

i=0 i++=0 i--=1
i=0 i++=-1 i--=0
9 8 7
9 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))。

 


view plaincopy to clipboardprint?
#include <iostream>   
using namespace std;   
int main() {   
cout << "aaa";   
printf("bbb");   
return 0;   
}  

#include <iostream> using namespace std; int main() { cout << "aaa"; printf("bbb"); return 0; }

 

输出为:
aaabbb
没有问题。
如果将程序修改一下:

 


view plaincopy to clipboardprint?
#include <iostream>   
using namespace std;   
int main() {   
ios::sync_with_stdio(false);   
cout << "aaa";   
printf("bbb");   
return 0;   
}  

#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” 之类的警告。
如果再修改一下程序:

 


view plaincopy to clipboardprint?
#include <iostream>   
using namespace std;   
int main() {   
ios::sync_with_stdio(false);   
cout << "aaa" << flush;   
printf("bbb");   
return 0;   
}  

#include <iostream> using namespace std; int main() { ios::sync_with_stdio(false); cout << "aaa" << flush; printf("bbb"); return 0; }

 

这回程序的输出就又正确了。因为flush强制清空了缓冲区,将其中的内容输出。

注意:在VS的环境中混用好像是没有问题的,所以这个问题还是跟具体环境有关~

 

本文转至:http://star23.yo2.cn/archives/5368

  评论这张
 
阅读(925)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017