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

发布网友 发布时间:2022-04-24 05:56

我来回答

5个回答

热心网友 时间:2023-08-19 10:32

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 ring 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 proce 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 99:1999

热心网友 时间:2023-08-19 10:32

对于你的问题的回答如下:(都是在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

其实我觉得你不必那么纠结于此了,还是好好看看别的吧。
哈哈 ,答了这么多,你可以把分给我吗?

热心网友 时间:2023-08-19 10:33

#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总有些出人意料的地方。昨天还被一个题给难倒了。

热心网友 时间:2023-08-19 10:33

我也只能作出很简陋的说明。
在p=(x++)+(x++)+(x++);中在进行加法运算的时候,由于尾++的运算优先级最低,所以x的值总是8,所以p=3*8=24;
在q=(++y)+(++y)+(++y);中由于在计算第一个加法的时候,头++的优先级最高,所以相当于10+10=20,然后进行第二个加法的运算,得到20+11=31

热心网友 时间:2023-08-19 10:34

不要探讨多重赋值的问题, 自加自减都有赋值动作,
这类的在标准中属于未定义行为, 所以不同的编译器结果不同, 具体要在编译器试试,
编写不依赖编译器的代码时不要用这类未定义行为
至于遇到这类脑残的题时,你可以在TC里试试,或着定义变量时增加volatile关键字

声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com