这几天论坛提的问题
求答案及解释,++--问题#include<stdio.h>
#include<string.h>
int main()
{
int a=4;
printf("%d %d %d %d\n",++a,--a,a++,a--);
return 0;
}
原帖:https://bbs.bccn.net/thread-360602-1-1.html
下面解答:
复制来自:http://
在这里我使用了编译的结果是:8,8,7,8
用TC2.0编译的结果是:8,7,7,8
VC6.0我没有安装,所以没有试过,也没办法分析。
这里我们可以看到,由不同的编译器产生了不同结果,可见这个问题是依赖编译器的理解和实现的。换句话说,对于i++和++i的处理本来就是非常具有歧义的,当然在自己应用中我相信也不会有任何程序员写出这样歧义的代码。但是作为一个问题,我们有必要分析一下不同编译器究竟如何理解i++和++i操作符的。
我们在学习C的时候,应该已经大概知道了i++和++i两者的区别,即“++”符号在i之前还是之后,决定了i自增操作和他的语句的执行顺序的关系。即i++,理解为i在其语句中取原始值,++i在其语句中取自增后的新值。这一点是毫无疑义的。但是问题在于,网友的问题中又涉及到了i++,++i在作为参数时候的处理,所以这时候我们就会感到困惑,i++和++i在作为参数的时候,和进入堆栈的顺序之间有何关系呢?根据前面的实验,可见TC2.0和的处理不同,可见两者对其处理不同,那么造成这种不同的结果的原因是什么呢?我们从代码上无法看到差异,因此我们必须看汇编语言才能知晓,编译器到底把我们的代码翻译成了什么墨阳。下面我采用IDA反汇编编译器把生成的.exe文件:由于程序很小,所以我们很容易找到相应的汇编代码(TC2.0的程序中有很多中断的相关代码,更加难以找到),结果如下:
2005的代码
可见,++i和--i执行的时候直接改变了i的值,而i++和i--必须在所在的这个语句执行后才能改变i的值,所以i++作为参数时,实际上是这样的过程,
printf("%d",i++);
相当于下面的语句:
int temp=i;
i=(i+1);
printf("%d",temp);
因此上面的代码可以翻译为:
int i=8;
printf("%d,%d,%d,%d",++i,--i,i++,i--);
因此可以翻译为下面的等效代码:
i=8;
temp0=i; //temp0=8;
i--; //7
temp1=i; //temp1=7
i++; //8
--i; //7
++i; //i=8
printf("%d,%d,%d,%d",i,i,temp1,temp0);
所以打印结果是8,8,7,8
---------------------------------------------------------------------------------------------------------
我们再看在TC2.0下的反汇编代码:
TC2.0下面的反汇编代码
可见,上面的行为可以翻译为下面的等效代码(TC2.0):
i=8;
temp0=i; //这时8已经入栈,实际上通过ax寄存器直接压栈里了~~~,不存在temp0)
i--; //i=7
temp1=i; //这时7已经入栈,实际上通过ax寄存器直接压栈里了~~~,不存在temp1)
i++; //i=8
--i; //i=7
temp2=i; //7已经入栈
++i; //i=8
temp3=i; //8已经入栈
printf("%d,%d,%d,%d",temp3,temp2,temp1,temp0);
输出结果是:8,7,7,8
----------------------------------------------------------------------------------------------------------------------
下面我们将总结和分析两种编译器的处理之间有何不同:@@@@@@
请注意两者的区别主要是,他们是一边处理自增自减并一边随时入栈,还是先处理完所有自增自减之后再最后统一一次性的入栈。
----------------------------------------------------------
【补充】:在这里这样总结他们的区别是不完善的,实际上涉及到(1)缓存 i 的值,(2)++/--运算符的执行,(3)push i 或 缓存值。
这三者之间的顺序问题。以上三者之间的顺序的微小差别都能对结果产生关键影响。请参考本文最后的补充。
--hoodlum1980 @ 2011年10月12日
----------------------------------------------------------
(1)在TC下面属于前者,每执行一个语句,就把i通过ax寄存器马上入栈了,所以参数入栈和i++等语句是交叉交替性进行的。这里的i++和++i的主要区别在于压栈是在i自增之前还是之后。
i++相当于:先入栈,再自增。
++i相当于,先自增,再入栈。
所以我们看到下面的参数:从右到左:
i--: 入栈8,i=7
i++:入栈7,i=8
--i:i=7,入栈7
++i:i=8,入栈8
所以导致栈里面的参数是8,7,7,8,所以打印结果是8,7,7,8.
(2)在上面的中属于后者,是先为i++和i--保存值,然后执行完所有的自增和自减,最后一次性的把所有参数入栈。在这里i++和++i的区别
主要是是否把i的值保存到另一个位置:而且最大不同点在于这里不马上入栈,而是等所有参数处理后统一入栈。
i++:先缓存i的原始值,然后i自增。最后入栈时,用i的原始值入栈。
++i:i自增,不缓存i的原始值。最后入栈时,是更新后的i。
所以我们看到在中的顺序是:
i--: 缓存8,i=7
i++:缓存7,i=8
--i:i=7
++i:i=8
参数一次性依次入栈:第一个缓存值8,第二个缓存值7,i的当前值8,i的当前值8。
所以这时候栈的数据是:8,8,7,8.(从左到右)。
所以打印结果是:8,8,7,8.
------------------------------------------------------------
【补充】对 VC6.0 的分析(如提问者所述:结果是 8,7,8,8)
--hoodlum1980 @ 2011-10-12
------------------------------------------------------------
现在补充下对 VC6.0 的debug版本的分析。本题目本质上是分析编译器如何处理入栈和++等操作符之间的关系。也就是说结果取决于编译器如何将参数push到栈中。VC6.0 的结果打印出来是 8,7,8,8。其汇编代码如下:
.text:00401028 mov [ebp+var_4], 8 ; int i = 8;
.text:0040102F mov eax, [ebp+var_4]
.text:00401032 mov [ebp+var_8], eax ; int tmp1 = i;
.text:00401035 mov ecx, [ebp+var_8]
.text:00401038 push ecx ; push tmp1; (8)
.text:00401039 mov edx, [ebp+var_4] ; int tmp2 = i;
.text:0040103C mov [ebp+var_C], edx
.text:0040103F mov eax, [ebp+var_C]
.text:00401042 push eax ; push tmp2; (8)
.text:00401043 mov ecx, [ebp+var_4]
.text:00401046 sub ecx, 1
.text:00401049 mov [ebp+var_4], ecx ; --i; (之后i = 7)
.text:0040104C mov edx, [ebp+var_4]
.text:0040104F push edx ; push i; (7)
.text:00401050 mov eax, [ebp+var_4]
.text:00401053 add eax, 1
.text:00401056 mov [ebp+var_4], eax ; ++i; (之后i=8)
.text:00401059 mov ecx, [ebp+var_4]
.text:0040105C push ecx ; push i; (8)
.text:0040105D push offset ??_C@_0N@KFBM@?$CFd?0?$CFd?0?$CFd?0?$CFd?6?$AA@ ; "%d,%d,%d,%d\n"
.text:00401062 mov edx, [ebp+var_4]
.text:00401065 add edx, 1
.text:00401068 mov [ebp+var_4], edx ; i++; (之后i=9)
.text:0040106B mov eax, [ebp+var_4]
.text:0040106E sub eax, 1
.text:00401071 mov [ebp+var_4], eax ; i--; (之后i=8)
.text:00401074 call printf
.text:00401079 add esp, 14h
复制代码
我们翻译上面的汇编代码的等效代码如下:
int i = 8;
int tmp1 = i;
int tmp2 = i; //临时变量 tmp1 和 tmp2 是实际上存在!
--i;
int tmp3 = i; //临时变量 tmp3 和 tmp4 是实际上不存在。本质是push i。
++i;
int tmp4 = i;
i++; //此处是处理语句中的两个后置操作符。
i--;
printf("%d,%d,%d,%d\n", tmp4, tmp3, tmp2, tmp1);
复制代码
这里我们注意到,VC6 本质上也是一边入栈一边处理前置运算符。VC6是先处理两个前置的运算符(++i和--i),并在此过程中就入栈参数,当参数入栈完毕后最后处理两个后置运算符(i++和i--)。同时 VC6 和 Vs2005的处理过程非常类似,但是不同在于处理顺序不同。VC6先处理前置运算符(--i 和 ++i),然后最后处理后置运算符(i++,i--),注意这个顺序是依照了参数入栈顺序(从右到左)。而VS2005和VC6的处理顺序相反。
【总结】
尽管我们分析了上面的汇编代码,但是实际上还是有些云里雾里。不同编译器产生不同的结果的本质原因在于:在处理++,--运算符和入栈之间的顺序关系,同时还需要了解的是,前置的运算符一般在执行本语句之前就执行,因此无需缓存值。而后置运算符通常的处理则通常需要缓存值。由于本问题中是对同一个变量多次使用++,--运算符,且对其入栈,因此运算符和入栈之间的顺序对结果有着关键影响。通过下表可以进行直观的对比:
int i = 8;
printf ( "%d,%d,%d,%d\n", ++i, --i, i++, i-- );
VC6.0
VS2005
TC2.0
tmp1 = i
push tmp1
tmp2 = i
push tmp2
--i
push i
++i
push i
i++
i--
tmp1 = i
i—
tmp2 = i
i++
--i
++i
push tmp1
push tmp2
push i
push i
tmp1 = i
i—
push tmp1
tmp2 = i
i++
push tmp2
--i
push i
++i
push i
8,7,8,8
8,8,7,8
8,7,7,8
备注:上面的 tmp1,tmp2 在 VC6和 VS2005中是栈上的实际临时变量(它们是由于两个后置运算符而产生的),在TC2.0中是使用AX寄存器中转。
仔细观察上面的表格中可以发现,push 了 4 个 i 的顺序是没有疑问的,由于有后置运算符,所以需要两个临时变量来缓存当前值。结果不同的原因在于,不同编译器,(1)缓存 i 的当前值,(2)处理 ++,--运算符 和 (3)push 语句之间的顺序不同,因此导致结果不同。
PS : 就算你明白你得不到什么。我想我们也不会写那样的代码,也不会出那样的题目。没有什么意义。。。
c 要怎么学习。 学好了c 我们能做什么?
我来谈谈自己一点小小的想法。
开始我们不要认为语言哪个好哪个差,这个没必要讨论。完全浪费时间。
高手都说语言是工具。 既然是工具何必要选择呢。只要需要 适合就可以了。
学好了一门,学习他的思想 学习他的模式。通过它来了解计算机。其他的工具自然会用了。
就像我们会QQ聊天工具,难道别的聊天工具我们还要学习怎么用他们。(除非编程者故意写的表态差不多)。。
C这门语言作为自己第一门语言 我觉得再好不过语言,
低一点看 可以考级,基本什么考试 c都是主要 我是说一般。。
长一点: 既可以了解底层 也可以有助于学习面向对象的语言 起码学习c++的时候非常方便了 自己感受。。
每个语言都有自己的优势,一个语言要能做什么,要看你自己的能力吧。你能力只能写垃圾 那他就只能写出垃圾。
c语言需要上培训吗?
你有钱的话 觉得钱对你完全没有概念的话。
上上也好 有老师好一点吧。
不管什么语言都是要多写 多看 多想 坚持 、
但要做到是多么难啊。
这几天写代码 写的蛋疼啊、
希望大家都都一起进步。。。。
完全是自己的一点想法而已、、说说而已 说的不对请指正。
但本人不会回