你真的理解了C语言++和--运算符么?

       这个主题对于刚开始学习C语言时可能会觉得很简单啊,那好你告诉我下面几个题目的输出是什么,你要是能说对,并且说出为什么,那你就可以不用往下看了

       int i = 0,j = 0;

       1、j = (i++)+(i++)+(i++);  //而不是j = i++i++i++;

       2、j = (++i)+(++i)+(++i); //而不是j = ++i++i++i;

       3、j = ++i+++i+++i;

       4、j = i+++j;

       下面我们一题一题来进行分析

       首先:我们来分析1和2两个题,这里需要稍微懂点汇编知识,因为C语言是分析不出来的,所以只能从汇编的角度去分析

但是不懂汇编语言也不用怕,因为我也不懂汇编语言,用到我都是百度查询,有的也不是很懂,下面我在VS2010里面编写上面代码

#include <stdio.h>
#include <string.h>
int main()
{
    int i = 0,j = 0;
	//第一题
    j = (i++)+(i++)+(i++);
	//下面我们自己分析下认为应该是
    //j=  0  +  1  +  2;  i = 3
    printf("i = %d,j = %d\n",i,j);  //实际输出i = 3 j = 0
    //第二题
    j = (++i)+(++i)+(++i);
	//下面我们自己分析下认为应该是
    //j = 4  +  5  +  6;   i = 6
    printf("i = %d,j = %d\n",i,j);  //实际输出i = 6 j = 18
	//第三题
	//j = ++i+++i+++i;               //编译出错
	//printf("i = %d,j = %d\n",i,j);
    //第四题
	j = i+++j;
	printf("i = %d,j = %d\n",i,j);
    system("pause");
    return 0;
}

再看下对应反汇编代码

#include <stdio.h>
#include <string.h>
int main()
{
003A34A0  push        ebp  
003A34A1  mov         ebp,esp  
003A34A3  sub         esp,0D8h  
003A34A9  push        ebx  
003A34AA  push        esi  
003A34AB  push        edi  
003A34AC  lea         edi,[ebp-0D8h]  
003A34B2  mov         ecx,36h  
003A34B7  mov         eax,0CCCCCCCCh  
003A34BC  rep stos    dword ptr es:[edi]  
    int i = 0,j = 0;
003A34BE  mov         dword ptr [i],0  
003A34C5  mov         dword ptr [j],0  
	//第一题
    j = (i++)+(i++)+(i++);
003A34CC  mov         eax,dword ptr [i]  
003A34CF  add         eax,dword ptr [i]  
003A34D2  add         eax,dword ptr [i]  
003A34D5  mov         dword ptr [j],eax  
003A34D8  mov         ecx,dword ptr [i]  
003A34DB  add         ecx,1  
003A34DE  mov         dword ptr [i],ecx  
003A34E1  mov         edx,dword ptr [i]  
003A34E4  add         edx,1  
003A34E7  mov         dword ptr [i],edx  
003A34EA  mov         eax,dword ptr [i]  
003A34ED  add         eax,1  
003A34F0  mov         dword ptr [i],eax  
	//下面我们自己分析下认为应该是
    //j=  0  +  1  +  2;  i = 3
    printf("i = %d,j = %d\n",i,j);  //实际输出i = 3 j = 0
003A34F3  mov         esi,esp  
003A34F5  mov         eax,dword ptr [j]  
003A34F8  push        eax  
003A34F9  mov         ecx,dword ptr [i]  
003A34FC  push        ecx  
003A34FD  push        offset string "i = %d,j = %d\n" (3A5A00h)  
003A3502  call        dword ptr [__imp__printf (3A82B0h)]  
003A3508  add         esp,0Ch  
003A350B  cmp         esi,esp  
003A350D  call        @ILT+295(__RTC_CheckEsp) (3A112Ch)  
    //第二题
    j = (++i)+(++i)+(++i);
003A3512  mov         eax,dword ptr [i]  
003A3515  add         eax,1  
003A3518  mov         dword ptr [i],eax  
003A351B  mov         ecx,dword ptr [i]  
003A351E  add         ecx,1  
003A3521  mov         dword ptr [i],ecx  
003A3524  mov         edx,dword ptr [i]  
003A3527  add         edx,1  
003A352A  mov         dword ptr [i],edx  
003A352D  mov         eax,dword ptr [i]  
003A3530  add         eax,dword ptr [i]  
003A3533  add         eax,dword ptr [i]  
003A3536  mov         dword ptr [j],eax  
	//下面我们自己分析下认为应该是
    //j = 4  +  5  +  6;   i = 6
    printf("i = %d,j = %d\n",i,j);  //实际输出i = 6 j = 18
003A3539  mov         esi,esp  
003A353B  mov         eax,dword ptr [j]  
003A353E  push        eax  
003A353F  mov         ecx,dword ptr [i]  
003A3542  push        ecx  
003A3543  push        offset string "i = %d,j = %d\n" (3A5A00h)  
003A3548  call        dword ptr [__imp__printf (3A82B0h)]  
003A354E  add         esp,0Ch  
003A3551  cmp         esi,esp  
003A3553  call        @ILT+295(__RTC_CheckEsp) (3A112Ch)  
	//第三题
	//j = ++i+++i+++i;               //编译出错
	//printf("i = %d,j = %d\n",i,j);
    //第四题
	j = i+++j;
003A3558  mov         eax,dword ptr [i]  
003A355B  add         eax,dword ptr [j]  
003A355E  mov         dword ptr [j],eax  
003A3561  mov         ecx,dword ptr [i]  
003A3564  add         ecx,1  
003A3567  mov         dword ptr [i],ecx  
	printf("i = %d,j = %d\n",i,j);
003A356A  mov         esi,esp  
003A356C  mov         eax,dword ptr [j]  
003A356F  push        eax  
003A3570  mov         ecx,dword ptr [i]  
003A3573  push        ecx  
003A3574  push        offset string "i = %d,j = %d\n" (3A5A00h)  
003A3579  call        dword ptr [__imp__printf (3A82B0h)]  
003A357F  add         esp,0Ch  
003A3582  cmp         esi,esp  
003A3584  call        @ILT+295(__RTC_CheckEsp) (3A112Ch)  
    system("pause");
003A3589  push        offset string "pause" (3A57B0h)  
003A358E  call        @ILT+445(_system) (3A11C2h)  
003A3593  add         esp,4  
    return 0;
003A3596  xor         eax,eax  
}

首先我们来分析第1个题:j = (i++)+(i++)+(i++);

前面一些初始化我就不讲了,我们直接对这句汇编进行分析

	//第一题
    j = (i++)+(i++)+(i++);
003A34CC  mov         eax,dword ptr [i]  
003A34CF  add         eax,dword ptr [i]  
003A34D2  add         eax,dword ptr [i]  
003A34D5  mov         dword ptr [j],eax  
003A34D8  mov         ecx,dword ptr [i]  
003A34DB  add         ecx,1  
003A34DE  mov         dword ptr [i],ecx  
003A34E1  mov         edx,dword ptr [i]  
003A34E4  add         edx,1  
003A34E7  mov         dword ptr [i],edx  
003A34EA  mov         eax,dword ptr [i]  
003A34ED  add         eax,1  
003A34F0  mov         dword ptr [i],eax 

这里面实际就用了两条汇编指令mov和add:

      mov指令:数据传输指令,用C语言的话讲就是赋值指令‘=’比如:mov AL,20H  相当于C语言就是  AL = 20H  AL是寄存器

      add指令:加法指令,用C语言的话讲就是一个复合赋值运算符指令‘+=’比如:add AX,8H  相当于C语言就是 AX += 8,再简单点就是AX = AX + 8

       eax,ebx,ecx,edx,esi,edi,ebp,esp:这些都是通用寄存器,用C语言的话讲就是全局变量(但是这些寄存器又有特殊用处,这里不详细讲,感兴趣可以百度)

      dword:双字  就是四个字节

      ptr:pointer 即指针

      []:里的数据是一个地址值,这个地址值指向一个双字型数据

      比如:mov  eax,dword ptr [12345678];把内存地址12345678中的双字型(32位)数据赋给eax,相当于C语言就是 exa = *12345678;

                mov  eax,dword ptr [i] ;把内存地址&i中的双字型(32)数据赋给exa,相当于C语言就是eax = i;

      好了知道这些汇编指令就可以分析了

003A34CC  mov        eax,dword ptr [i] ;  相当于C语言中 eax = i;因为i = 0,所以eax = 0 
003A34CF  add         eax,dword ptr [i] ;  相当于C语言中 eax = eax + i; 因为i = 0,eax = 0,所以 eax = eax + i  = 0
003A34D2  add         eax,dword ptr [i] ;  相当于C语言中 eax = eax + i; 同上,eax = 0
003A34D5  mov        dword ptr [j],eax ;  相当于C语言中 j = eax; 因为eax = 0,所以 j = 0

所以前四条汇编指令执行完,j = 0,再往下面分析

003A34D8  mov         ecx,dword ptr [i] ; 相当于C语言中 ecx = i;因为i = 0,所以ecx = 0
003A34DB  add         ecx,1                    ; 相当于C语言中 ecx = ecx + 1 ; 所有ecx = 1
003A34DE  mov         dword ptr [i],ecx ; 相当于C语言中 i = ecx;所以i = 1 
003A34E1  mov         edx,dword ptr [i] ; 相当于C语言中 edx = i;因为i = 1,所以edx = 1
003A34E4  add         edx,1                    ; 相当于C语言中 edx = edx + 1 ; 所有edx = 2
003A34E7  mov         dword ptr [i],edx ; 相当于C语言中 i = edx;所以i = 2 
003A34EA  mov         eax,dword ptr [i] ; 相当于C语言中 eax = i;因为i = 2,所以eax = 2
003A34ED  add         eax,1                    ; 相当于C语言中 eax = eax + 1 ; 所有eax = 3

003A34F0  mov         dword ptr [i],eax ;  相当于C语言中 i = eax;所以i = 3

所以通过上面分析,j = 0,i = 3;

这个分析完全和我们注释的分析是不一样的

    好了我们在分析第2题(别忘了j = 0,i = 3)

    j = (++i)+(++i)+(++i);
003A3512  mov         eax,dword ptr [i]  ; 相当于C语言中 eax = i;因为i = 3,所以eax = 3
003A3515  add         eax,1                     ; 相当于C语言中 eax = eax + 1 ; 所有eax = 4
003A3518  mov         dword ptr [i],eax  ; 相当于C语言中 i = eax;所以i = 4
003A351B  mov         ecx,dword ptr [i]  ; 相当于C语言中 ecx = i;因为i = 4,所以ecx = 4
003A351E  add         ecx,1                     ; 相当于C语言中 ecx = ecx + 1 ; 所有ecx = 5
003A3521  mov         dword ptr [i],ecx  ; 相当于C语言中 i = ecx;所以i = 5 
003A3524  mov         edx,dword ptr [i]  ; 相当于C语言中 edx = i;因为i = 5,所以edx = 5
003A3527  add         edx,1                     ; 相当于C语言中 edx = edx + 1 ; 所有edx = 6
003A352A  mov         dword ptr [i],edx ; 相当于C语言中 i = edx;所以i = 6
003A352D  mov         eax,dword ptr [i] ; 相当于C语言中 eax = i;因为i = 6,所以eax = 6
003A3530  add         eax,dword ptr [i]   ; 相当于C语言中 eax = eax + i ; 所有eax = 12
003A3533  add         eax,dword ptr [i]   ; 相当于C语言中 eax = eax + i ; 所有eax = 18

003A3536  mov         dword ptr [j],eax  ; 相当于C语言中 j = eax;因为eax = 18,所以j = 18

通过上面分析,i = 6,j = 18

这个分析完全和我们注释的分析也是不一样的

        好了我们接着分析第3题 (别忘了,i = 6)

        j = ++i+++i+++i;

        看看这个表达式是不是就是第2题的表达式去掉大括号啊,还真是啊

        因为编译不通过,就没有办法通过反汇编分析了,所以只能从C语言角度分析了

       分析过程:首先编译器读取第一个字符‘+,这时编译器可能认为这是一个加法,也可能认为是一个自增运算符,所以编译器还会往后面读取,再读取一个字符‘+’,这时编译器就可以判断出来了,这是一个自增运算符,而且后面肯定有一个变量在后面跟着,否则编译出错,所以再读取一个字符‘i’,总结前面就是执行了一个“++i”,然后往下分析,这时编译器往后面读取字符‘+’,这时编译器可能认为是加法,也可能认为是自增运算符,所以编译器还得往后面读取才能知道到底是什么字符,这时编译器再读取一个字符‘+’,这时编译器就能判断出来了,这是一个自增运算符,同时编译器也会报错,为什么会报错呢,因为前面++i,执行完是一个常数7,7后面又跟了自增运算符,相当于7++,这里肯定是错误的,因为自增或者自减运算符只能对变量执行,不能对常数,所以编译肯定报错的

       上面编译器处理的方法叫做“贪心法”,编译器通过贪心法处理表达式中的子表达式 

       有人可能认为你这些都是你瞎猜的,谁知道你分析的对不对,又没有对应反汇编代码,所以我们在VS里面再加上一条语句

        j = 7++;

        看它是否和第3题的错误提示信息是否是一样,如果是一样的,就说明我们的分析是对的


  看见没,是一样的错误信息,所以我们的分析完全正确,我同时也在ubuntu 10里面试了下,

    

       gcc 提示信息:test.c:17: error: lvalue required as increment operand,中文意思:左值必须是一个变量操作数

       讲的有点累了,说的也比较啰嗦,好了我们再分析下第4题(别忘了i = 6,j = 18)

       j = i+++j;

       这个表达式就会有两种结果:

                                                 第1种:j = (i++) + j;

                                                 第2种:j = i + (++j);

      我们这次采用两种方法讲解:

                                                第一种:直接从C语言用“贪心法”分析

                                                第二种:从反汇编角度去分析

     第一种:首先编译器读取i++,执行i++,然后在往后面读取字符‘+’这时编译认为可能是加法,也可能是自增运算符,所以还得往后面读取字符才能知道,再次读取一个字符‘j’,这时编译器就判断出来了,这是一个加法,所以编译器先执行,i++,然后在加上j,执行结果就是i = 7,j = 24,也就是它是按第1种情况执行的

     第二种:从反汇编角度去分析

003A3558  mov         eax,dword ptr [i] ;  相当于C语言中 eax = i;因为i = 6,所以eax = 6
003A355B  add         eax,dword ptr [j]  ; 相当于C语言中 eax = eax + j ; 因为j = 18,所有eax = 24
003A355E  mov         dword ptr [j],eax ; 相当于C语言中 j = eax;所以j = 24
003A3561  mov         ecx,dword ptr [i] ; 相当于C语言中 ecx = i;因为i = 6,所以ecx = 6
003A3564  add         ecx,1                    ; 相当于C语言中 ecx = ecx + 1 ; 所有ecx = 7

003A3567  mov         dword ptr [i],ecx ; 相当于C语言中 i = ecx;所以i = 7

      通过上面两种方法分析,i = 7,j = 24

     两种分析方法里面具体执行细节还是不一样的,反汇编肯定是最详细的,最权威的,C语言还是不够详细的,比如这道题里面,执行i++时,表面感觉,结果肯定是6,执行完时i本身已经变成7了么?显然是看不出来的,只有通过反汇编我们才知道,实际i本身值没有变成7,而是最后才变成7的

     这里好了,我们再硬插一道第5题(不然还得写一篇博客)


       看见没,第五题就是b = b/*p;p是指向整型a的变量,但是你如果不注意,输入完毕,直接编译,编译出错

这时你在仔细回去看下代码,发现b = b/*p;后面语句都是绿色了,都注释掉了,这是为什么,其实这是因为编译器把/*p当成‘/*’注释符了,所以后面全都注释掉了,那难道就没有办法解决么?实际是可以解决的,你把除号后面加一个空格就可以了,b = b/ *p;

       开始对上面进行总结

1、++和--操作符在混合运算中的行为可能不同

2、++和--对应汇编指令不一定连续执行

3、在混合运算中,++和--的汇编指令可能被打断执行

4、编译器通过“贪心法”处理表达式中的子表达式

5、空格可以作为C语言中的一个完整符号的休止符

6、编译器读入空格后立即对之前读入的符号进行处理


这里面还有很多关于++,--的一些坑

比如:printf("%d,%d\n",i++,i++);

          printf("%d,%d\n",i++,i);

          你认为上面输出会是一样的么?

         i = 1;

         j = ++i+i+++i;

         printf("j = %d\n",j); //j等于几,还是说编译出错

这些你都可以通过汇编去分析

参考资料:狄泰软件C语言进阶教程


  • 24
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值