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

时间: 2024-11-08 09:02:08

Cout vs printf---缓存与引用,流处理顺序(转ithzhang,知乎郝译钧)的相关文章

重定向C库中stdio文件中的printf()函数,文件流--&gt;串口USART1

6.4 一些说明 数据属性可以重写同名的方法属性.这是为了避免在大型系统中产生问题的意外名称冲突.所以用一些减少冲突的常用方法是很有效果的.常用的方法包括:大写字母方法名称,用唯一的字符串来做为数据属性的名称(可以是个下划线_)或者用动词命名方法和用名字命名数据属性. 数据属性就像和对象的普通用户一样可以被方法引用.换句话说,类不能用来实现纯净的数据类型.事实上,在python中不能强制数据隐藏,一切基于约定.(另一方面,如C中写的,python的实现可以做到完全隐藏实现细节并且在必要是可以控制

(BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明

原文:http://blog.csdn.net/java_jh/article/details/20068915 迟点出更新的.这个还有BUG.因为软引应不给力了.2.3之后 前几天的原文有一个线程管理与加载源过多,造成浪费流量的问题.下面对这进下改进的一些说明(红色为新加) 这两天一直在优化这个问题.google也很多种做法.但发现都是比较不全面. 比如: 一些只实现了异步加载,却没有线程池与软引用. 一些是用AsynTast的, 一些有了线程池但加载所有的图片,这样造成具大资源浪费 一些是用

68. 缓存输入输出字符流

输入字符流:--------------| Reader 输入字符流的基类. 抽象类----------| FileReader 读取文件的输入字符流----------| BufferedReader 缓存输入字符流(提高效率和扩展了FileReader的功能).内部其实也维护了一个字符数组 扩展功能:readLine()     一次读取文本的一行数据,如果读取到了文件末尾返回null 输出字符流:--------------| Write  输出字符流的基类. 抽象类----------|

c语言中printf()函数中的参数计算顺序

今天看到了一个关于printf()函数计算顺序的问题,首先看一个例子: #include<stdio.h> int main() { printf("%d---%d---%d",printf("ab"),printf("c"),printf("eee")); } 输出结果为: 这说明printf()函数在计算的时候顺序是从右往左的,但最后输出的顺序是从左往右的.所以遇到类似题目时,应该根据运算顺序和输出顺序灵活判断

java8新特性——并行流与顺序流

在我们开发过程中,我们都知道想要提高程序效率,我们可以启用多线程去并行处理,而java8中对数据处理也提供了它得并行方法,今天就来简单学习一下java8中得并行流与顺序流. 并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流. Java8中将并行流进行了优化,我们可以很容易的对数据进行并行操作.Stream API可以声明性地通过parallel()与scqucntial()在并行流与顺序流之间进行切换. 一.Fork-Join框架 Fork-Join框架:是java7提供

RecursiveTask和RecursiveAction的使用 以及java 8 并行流和顺序流(转)

什么是Fork/Join框架        Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 我们再通过Fork和Join这两个单词来理解下Fork/Join框架,Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后得到这个大任务的结果.比如计算1+2+..+10000,可以分割成10个子任务,每个子任务分别对1000个数进行求和,最终汇总这10个子任

cout和printf

#include <iostream> using namespace std; int main() { int i=0,j=0; cout<<i++<<"\t"<<i++<<"\t"; cout<<i++<<endl; printf("j++\tj++\t"); printf("j++\n"); return 0; } VC6.0 #inc

cout和printf不能混用

1.两者的缓存机制不同:printf无缓冲区,而std::cout有 (其实printf也是有缓冲区的,https://blog.csdn.net/ithzhang/article/details/6875176) 2.对于标准输出的加锁时机不同:printf在对标准输出作任何处理前先加锁:std::out在实际向标准输出打印时才加锁 3.二者存在微弱的时序差别,而在多线程环境下,很多问题就是由于微弱的时序差别造成的.所以两者混用很容易带来不可预知的错误,常见的错误有打印输出的结果不符合预期,而

C++ 多线程中使用cout还是printf

在多线程的设计模式下,如果多个线程都使用cout打印信息,那么很容易出现内容交替的现象,例如下图: 代码如下: 如果把cout替换成printf,那么就不会出现这个问题,运行结果如下图: 对应代码如下: 上网搜索了下相关的内容,部分网友反馈结果是: cout不是线程安全的,要靠自己去线程同步,比较麻烦 .printf是线程安全的,也就是自己做了线程同步的处理. 这个结果有待继续考证,暂且记在这. 发现一篇对IOstream解释得比较好的文章,有一定的参考价值:http://www.cnblogs