cout和printf的缓冲机制

众所周知,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强制清空了缓冲区,将其中的内容输出。

阅读更多
个人分类: 我的程序员之路
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭