深入探讨C语言中printf函数与自加自减的问题!

先通过引用一个简单例子进入我的问题吧:#include<stdio.h>main(){inti=1;printf("%d%d\n",i++,++i);}这道题我用VC++... 先通过引用一个简单例子进入我的问题吧:

#include<stdio.h>
main()
{
int i=1;
printf("%d %d\n",i++,++i);
}
这道题我用VC++6.0编译结果是2 2 ,我的思路是从printf函数的参数自右向左计算,同样是这道题,就在我初学C语言的时候,没记错的话得的结果却是1 3,printf中结合自加自减的运算一直捆饶着我,学了数据结构和汇编之后,我觉得应该是入栈出栈导致的,参数从左开始,根据先进后出的特点,从右开始计算,所以得出2,2

继续看一道题
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int* p = a;
printf("%d %d \n", (*(++p))++, *p++);
printf("%d\n", a[2]);
return 0;
}
我用刚才的思路考虑,得出的结果为2 1 3,与VC++6.0结果一致,但是为什么有的编译器为什么结果却是3 1 4(在知道上看的,不知道哪个编译器) ?我很奇怪,是我之前的思路有问题,在理解自加自减上,先用再加/减的处理时机不对,还是编译器的原因?系统的学习C语言是用的谭老师的绿色第四版C语言教材

按我之前的理解下面这道题结果在VC++6.0下也不对了

//洪恩教育天津科学技术出版社新编C语言程序设计入门P30例2.8,我第一本C语言书
#include<stdio.h>
main()
{
int p,q;
int x=8,y=8;
p=(x++)+(x++)+(x++);
q=(++y)+(++y)+(++y);
printf("%d %d %d %d\n",p,q,x,y);
}

我的结果是27 27 11 11,而VC结果却是24 31 11 11

我个人学C语言有段时间了,这个问题是自开始学到现在一直没完全弄懂的唯一一个问题,今天想通过百度知道这个平台,做为一个典例那出来,彻底解决这个问题,也为后人留个参考,希望权威人士可以帮忙,小弟再此谢过!
展开
 我来答
FrankHB1989
推荐于2016-07-04 · TA获得超过4.2万个赞
知道大有可为答主
回答量:4569
采纳率:100%
帮助的人:3162万
展开全部
LS注意,不是“没有明确规定”,而是明确规定LZ的这种用法会导致未定义行为(undefined behavior)。未定义行为基于用户对语言或数据的错误使用,具有未定义行为的程序的行为完全不可靠(就算是导致编译器崩溃也是自找的——虽然不会有厂商会让编译器表现出这种不可靠性)。被ISO C/C++定义为未定义行为的用法,可以认为不论给出什么明确的结果都是愚蠢的,于是标准不对其行为作出任何保证,用户也不应该指望有任何结果。
会出现这种问题又不说清楚undefined behavior的教材根本就是不合格。
对于这里的用法为什么属于未定义行为的严格依据(LZ需要补关于序列点的知识):
ISO C99:
3.4.3
1 undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data,
for which this International Standard imposes no requirements
2 NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
3 EXAMPLE An example of undefined behavior is the behavior on integer overflow.
4. Conformance
2 If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint is violated, the
behavior is undefined. Undefined behavior is otherwise indicated in this International
Standard by the words ‘‘undefined behavior’’ or by the omission of any explicit definition
of behavior. There is no difference in emphasis among these three; they all describe
‘‘behavior that is undefined’’.
5.2.1.3
2 Accessing a volatile object, modifying an object, modifying a file, or calling a function
that does any of those operations are all side effects,11) which are changes in the state of
the execution environment. Evaluation of an expression may produce side effects. At
certain specified points in the execution sequence called sequence points, all side effects
of previous evaluations shall be complete and no side effects of subsequent evaluations
shall have taken place. (A summary of the sequence points is given in annex C.)
6.5
2 Between the previous and next sequence point an object shall have its stored value
modified at most once by the evaluation of an expression. Furthermore, the prior value
shall be read only to determine the value to be stored.
题外话,关于表达式求值顺序是未确定行为(unspecified behavior),可以是正确的行为,但具体行为取决于实现(这里就是编译器),不能与此混淆。而入栈出栈顺序涉及函数的调用约定(calling convention),这并不是在语言的规范中决定的,需要找其它的(例如硬件架构)编程模型规范(包括ABI)定义——事实上,C语言根本就没明确函数必需使用栈实现(尽管所有的主流硬件架构乃至虚拟机实现类C语言都使用栈来表达内建静态函数的行为)。 至于结合性,则彻底可以从表达式的语法规则(一大长串,不引用了,太罗嗦)完全、明确地推导出来,只是用来说明哪个操作数进行哪个运算,和具体求值的顺序之类更是没关系。
追问
那除去编译器导致的原因不考虑,printf函数的执行过程实质是怎样的?,求解顺序如何?出栈入栈与函数的调用约定是怎样的关系?
追答
包含
p=(x++)+(x++)+(x++);
q=(++y)+(++y)+(++y);
这种语句明确会引起未定义行为的程序,除了实际编译器的表现以外真没什么意义了。
而且,真要看实际执行过程,不是C语言层面上能完全决定的,需要看编译器生成的代码。
printf("%d %d %d %d\n",p,q,x,y);本身可以是对的,不过之前有未定义行为就不可预测了。如果假定前面都正确,那么p、q、x、y、"%d %d %d %d\n"这五个参数表达式会被求值。注意,参数求值没有明确的先后顺序——这是未确定行为,只是这里不确定性不会从输出结果体现出来;而像printf("%d %d %d %d\n",x,y,++x,++y);这样的语句的输出结果也能体现出不确定性——甚至包括用同一个编译器编译同一个程序中给定同样的x和y值的不同位置的此条相同语句。
函数参数求值的未确定行为的积极意义在于,编译器可以在局部重新排列代码顺序进行优化,减少代码体积或提升执行效率。如果几个参数之间有依赖关系,那么用户不应写成会影响预期结果的调用形式。
具体架构的具体调用约定中会约定实现细节,包括入栈顺序、由主调函数还是被调函数清理调用栈等。以x86为例,常见有cdecl、stdcall等。printf是不定参数个数的函数,所以有特殊限制,需要主调函数清理栈(被掉函数不知道具体有几个参数),典型实现为cdecl,函数参数从右至左进栈,返回值在EAX寄存器(这里是int;浮点返回值另作处理)。这些细节并不能由C语言直接保证(除非使用非标准扩展关键字,例如MSVC的__cdecl等,进行指定),反汇编会比较清楚一点。

参考资料: 原创

参考资料: 原创 + ISO 9899:1999

injer123
2011-08-11
知道答主
回答量:2
采纳率:0%
帮助的人:3万
展开全部
对于你的问题的回答如下:(都是在linux 下运行后的答案)
#include<stdio.h>
main()
{
int i=1;
printf("%d %d\n",i++,++i);
}
这道题我用VC++6.0编译结果是2 2 ,我的思路是从printf函数的参数自右向左计算,// 就是因为是从右到左才是2 2.因为首先++i,则此时i的值为2, 然后计算i++,因为是在下一次运行i++时i值为3,此时应该仍为2;
因此对于此题运行结果应为2 2 ,而不是1 3;
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int* p = a;
int i;
printf("%d %d \n", (*(++p))++, *p++);
for(i=0;i<5;i++)
printf("%4d\n", a[i]);
return 0;
}
我运行的结果是 3 1
1
2
4
4
5
即,按照你的说法是后面一种 是3 1 4;我的理解是
(*(++p))++, *p++); 从右到左:首先执行*p++,就是先执行*p, 然后p++,所以得1,然后P指向了a[1],接着*(++p)即p指向了啊【2】,求值得3,然后a【2】++,即a[2]自加,但是输出时并未加,此时输出值为3,最后执行输出a[2],即为4了,因此答案为 3 1 4;
另外我在vc6.0上运行答案也是 3 1 4啊 ,不是你说的 什么 2 1 4????

#include<stdio.h>
main()
{
int p,q;
int x=8,y=8;
p=(x++)+(x++)+(x++);
q=(++y)+(++y)+(++y);
printf("%d %d %d %d\n",p,q,x,y);
}

至于这道题,我在linux及vc6.0中都是 24 31 11 11
分析
引用以上一人回答:
我也只能作出很简陋的说明。
在p=(x++)+(x++)+(x++);中在进行加法运算的时候,由于尾++的运算优先级最低,所以x的值总是8,所以p=3*8=24;
在q=(++y)+(++y)+(++y);中由于在计算第一个加法的时候,头++的优先级最高,所以相当于10+10=20,然后进行第二个加法的运算,得到20+11=31

其实我觉得你不必那么纠结于此了,还是好好看看别的吧。
哈哈 ,答了这么多,你可以把分给我吗?
已赞过 已踩过<
你对这个回答的评价是?
评论 收起
师韩馀怀43
2011-08-16
知道答主
回答量:31
采纳率:0%
帮助的人:16.9万
展开全部
#include<stdio.h>
main()
{
int p,q;
int x=8,y=8;
p=(x++)+(x++)+(x++);
q=(++y)+(++y)+(++y);
printf("%d %d %d %d\n",p,q,x,y);
}
个人认为:p=(x++)+(x++)+(x++);中:x++这也操作是在该语句结束之后进行的,也就是3个x++都是在p=x+x+x之后执行。依据是X++是先用X的值,等语句结束后才自加,就像j=i++;是先j=i;再i++的。
q=(++y)+(++y)+(++y);
同理,如果j=++i;是先i++;再j=i的。所以其实q=(++y)+(++y)+(++y);
是q=9+10+11;第一个Y++之后y=9,第二个时y=10,第三个y=11;
同样的,个人认为:
#include<stdio.h>
main()
{
int i=1;
printf("%d %d\n",i++,++i);
}
中,执行printf("%d %d\n",i++,++i);
时,i++实在printf("%d %d\n",i++,++i);
之后执行,而++i是在printf("%d %d\n",i++,++i);
打印之前执行,i自加完成后i=2。然后打印,再i++。
至于:
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int* p = a;
printf("%d %d \n", (*(++p))++, *p++);
printf("%d\n", a[2]);
return 0;
}
这一个,我就不太明白了,不过可以肯定,printf("%d\n", a[2])绝对是3,因为自始自终都没对a[2]进行过任何操作。

以上是我个人的理解。最近发现VC++6.0总有些出人意料的地方。昨天还被一个题给难倒了。
已赞过 已踩过<
你对这个回答的评价是?
评论 收起
huguangchaoren
2011-08-10 · TA获得超过814个赞
知道小有建树答主
回答量:74
采纳率:0%
帮助的人:116万
展开全部
我也只能作出很简陋的说明。
在p=(x++)+(x++)+(x++);中在进行加法运算的时候,由于尾++的运算优先级最低,所以x的值总是8,所以p=3*8=24;
在q=(++y)+(++y)+(++y);中由于在计算第一个加法的时候,头++的优先级最高,所以相当于10+10=20,然后进行第二个加法的运算,得到20+11=31
已赞过 已踩过<
你对这个回答的评价是?
评论 收起
ssilspro
2011-08-10 · TA获得超过2625个赞
知道大有可为答主
回答量:1148
采纳率:0%
帮助的人:1724万
展开全部
不要探讨多重赋值的问题, 自加自减都有赋值动作,
这类的在标准中属于未定义行为, 所以不同的编译器结果不同, 具体要在编译器试试,
编写不依赖编译器的代码时不要用这类未定义行为
至于遇到这类脑残的题时,你可以在TC里试试,或着定义变量时增加volatile关键字
已赞过 已踩过<
你对这个回答的评价是?
评论 收起
收起 更多回答(15)
推荐律师服务: 若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询

为你推荐:

下载百度知道APP,抢鲜体验
使用百度知道APP,立即抢鲜体验。你的手机镜头里或许有别人想知道的答案。
扫描二维码下载
×

类别

我们会通过消息、邮箱等方式尽快将举报结果通知您。

说明

0/200

提交
取消

辅 助

模 式