文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>变态的C表达式

变态的C表达式

时间:2009-03-11  来源:wxju168

#if 0

    变态的C表达式

    前阵子与X-man逛书店,一边斜眼看着MM,一边盯着可以找到什么好书。突然,X-man以迅雷不及掩耳盗铃之势出招了:“俺们上学时,有道C语言题, 没有学生可以答对,我说你听听?!”。C语言,也算俺看家本领了,面对挑战不能畏惧,只有奋勇直前,“什么玩意儿,这么强?!”。只见X-man不慌不忙,慢慢悠悠地说道:“i初值等于1,加加i加上加加i等于i,最后i等于多少?”,嗯,俺暗付改绕口令了不成?我想了想,道“2+3,等于5呗”,遂见X-man脸上露出小人得意的笑容,“正确答案是6!”,Faint,中招了。
    
    而后没也没太意这个问题,因为谁要是在产品代码写这种表达式,准得挨50屁板儿,然后逐出本派师门。这天晚上在家里、又突然想起这件事,心想权当智力测验了。

    下面,是我层层剥茧之后,总结出来的“代码介绍语言”,估计已经可以解释为什么上面的代码等于6了。诚然,文字说明更直观。不过,俺懒呀,大热的天实在不想敲太多文字了。
    
    越是熟悉的角落,越是隐藏着不易发现的秘密。

    OK,最后,还是写一些提示吧:

        1、局部变量i,是保存在栈上的,没有拷贝!
        2、不要试图编译成汇编代码分析,它们对你理解代码没有什么帮助,这招儿我试过了,后来想想也是不应该有用~
        3、后缀++,和前缀++的求值时间是理解以下程序的另一个关键点。
        4、程序中的注释,也是一些提示。嗯?你觉得它们更像谜语?猜吧。
        5、开动你的脑筋,只有自己想出来的答案,记忆才是最深刻的。
        
#endif

#include <stdio.h>

int
main()
{
    int i;

    i = (i=2)+(i=10); /* 10 + 10 */
    printf("%d\n", i);//20


    i = 1;
    i = (++i)+(i=10); /* 10 + 10 */
    printf("%d\n", i);//20


    i = 1;
    i = (i++)+(i=10); /* 10 + 10 + 1 */
    printf("%d\n", i);//21


    i = 1;
    i += ++i; /* 2 += 2 */
    printf("%d\n", i);//4


    i = 1;
    i += i++; /* 1 += 1 + 1 */
    printf("%d\n", i);//3


    i = 1;
    i = (++i)+(++i); /* 3 + 3 */
    printf("%d \n", i);//6


    i = 1;
    i = (++i)+(i++); /* 2 + 2 + 1 */
    printf("%d\n", i);//5


    i = 1;
    i = (i++)+(++i); /* 2 + 2 + 1 */
    printf("%d\n", i);//5


    i = 1;
    i = (i++)+(i++); /* 1 + 1 + 1 + 1 */
    printf("%d\n", i);//4


    i = 1;
    i = (++i)+(i++, i); /* 3 + 3 */
    printf("%d\n", i);//6


}
     
下面关于自增运算符处理的细节问题,问题如下:

int i = 0;
i = (i++) + (++i);
i = ?
那天小帆同学在群里问起,我图方便,顺手写了个脚本如下:

<html>
<script>
var i = 0;
i = (i++)+(++i);
alert(i);
</script>
</html>


执行该脚本计算得i=2;不想小帆却告诉我i=3!?并给出VC的执行过程:
9: int i=0;
0040B488 mov dword ptr [ebp-4],0
10: i=(i++)+(++i);
0040B48F mov eax,dword ptr [ebp-4] (++i开始)
0040B492 add eax,1 (++i)
0040B495 mov dword ptr [ebp-4],eax (++i结束)
0040B498 mov ecx,dword ptr [ebp-4]
0040B49B add ecx,dword ptr [ebp-4] (i+i,i已执行++i操作,这里是1+1)
0040B49E mov dword ptr [ebp-4],ecx (i=2的赋值操作)
0040B4A1 mov edx,dword ptr [ebp-4] (i++开始)
0040B4A4 add edx,1 (i++)
0040B4A7 mov dword ptr [ebp-4],edx (i++结束)
如果您能看懂以上代码就知道为什么i=3而不等于2了,i的值在最后进行处理,改变了最后的结果。
如果改成如下过程:
int i = 0;
j = (i++) + (++i);
j = ?
汇编码如下:
17: j=(i++)+(++i);
0040B7D9 mov ecx,dword ptr [ebp-4]
0040B7DC add ecx,1
0040B7DF mov dword ptr [ebp-4],ecx
0040B7E2 mov edx,dword ptr [ebp-4]
0040B7E5 add edx,dword ptr [ebp-4]
0040B7E8 mov dword ptr [ebp-8],edx (j=2赋值,注意,结果被放在了高8位)
0040B7EB mov eax,dword ptr [ebp-4] (i++仍然放在最后执行)
0040B7EE add eax,1
0040B7F1 mov dword ptr [ebp-4],eax
很显然,j=2!
通过对比我们应该知道:在表达式的执行过程中,i++操作是放在最后执行的,甚至在"="赋值操作之后。
真相大白,hoho,搞清楚了个问题,值得高兴。
如果再不信,去试试:
i = (i++) + (i++),i是不是等于2? (0+0,接着i两次自加)
j = (i++) + (i++),j是不是等于0? (i没自加前把值赋给了j)
就知道了:)
至于在javascript里执行结果为什么是3?仿佛我对js没什么兴趣,有兴趣的同学去研究吧。
-------------------------------------------------------------------------
Linux版本:
源代码:

#include <stdio.h>

int main()
{
  int i = 1, j = 1, k;

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

  k = (j++) + (++j);
  printf("j=%d, k=%d\n", j, k);
}

执行结果:

i=5
j=3, k=4


关键的汇编码片段:

 movl $0x1,0xfffffff0(%ebp)
 movl $0x1,0xfffffff4(%ebp)
 addl $0x1,0xfffffff0(%ebp)
 mov 0xfffffff0(%ebp),%eax
 add %eax,0xfffffff0(%ebp)
 addl $0x1,0xfffffff0(%ebp)
 mov 0xfffffff0(%ebp),%eax
 mov %eax,0x4(%esp)
 movl $0x8048500,(%esp)
 call 80482f8 <printf@plt>
 addl $0x1,0xfffffff4(%ebp)
 mov 0xfffffff4(%ebp),%eax
 add 0xfffffff4(%ebp),%eax
 mov %eax,0xfffffff8(%ebp)
 addl $0x1,0xfffffff4(%ebp)
 mov 0xfffffff8(%ebp),%eax
 mov %eax,0x8(%esp)
 mov 0xfffffff4(%ebp),%eax
 mov %eax,0x4(%esp)
 movl $0x8048507,(%esp)
 call 80482f8 <printf@plt>


明白了以后这就不难解释为什么i=1,执行(++i)+(++i)之后为什么i=6了。
 
1.

   char *str="xxx"

   //……一系列操作,比如str++;

   printf("%lu", str);  //注意,这里是以无符长整形的格式打印出str的地址,

                             //如果不注意会把它当成printf("%lu", *str);打印str当前字符,我就犯了这样的错

2.

   int a = 0;             // 数据段上有全局变量a=0

   void fun(int b)       // 栈上的b获得传给它的值0

   {

      b = 1;              // 栈上的值变了,但数据段上的global a的值并没有改变

      return;

   }

   int main()

   {

      fun(a);            

      printf("%d", a);  //当然该打印出0啦,让你不注意看~~

   }

3.

  void fun(char str[])  // 会被解释为char * str

  {

     printf("%d", sizeof(str));  // 打印"4",既然都知道参数会被解释成char *,为什么还会填错?真不明白我自己

  }

  int main()

  {

      char str[]="123456";

      fun(str);

  }

4.

  int x = 3;

  int main()

  {

    int i;

    for(i=1; i<3; i++)    // 套1,这里只迭代了2次,发现了

        fun();

  }

  void fun()

  {

    static int x=1;  // 套2,static变量,分配在数据段上而不是在栈上,OK了,套3,覆盖全局变量的作用域,OK

    x*=(x+1);        // 套4,中了

    printf("%d", x);

  }

  x=?

 这道题共4个套,过了3个,x*=(x+1)其实和i+++++i一样有点无聊,类似的代码我是不敢写,答案是2   6

 计算过程简化如下:

                       1) 取x所在寄存器中的值,该值+1

                       2)上面的结果放入另一个寄存器(你可以把它理解成临时变量)

                       3)再取x所在寄存器中的值*上面的结果

                       4)上面步骤的结果放回x所在寄存器,x值这才改变

5.

   char *str="123456";

   char *dest= (char *)malloc(6);

   memcpy(dest, str, 6);    // 注意,字符串结尾的'\0'没拷贝过去,以此来做结束标志的strlen()废了

   printf(dest);结果是什么?

   strlen(dest)=?

   说实在的,我从没这么用过,结果是什么我想都不敢想……

6.

   free(ptr);

   if(NULL != ptr)

     strcpy(ptr, "123");

   结果是什么?

   偶不敢想呐,free完的空悬指针还敢掏出来玩

7.

   sizeof()和strlen()计算长度的区别是什么?

   strlen()计算的是参数所指的字符串的长度,以'\0'为结束标志

   sizeof()计算的是参数类型的长度,千万记得补充一句,如果为数组或结构,计算的是所有元素加起来的长度,我就漏了这句,被BS了一下……

   

 

而跳过去的套套如下:

1.返回函数中的指针类型的本地变量错误

2.试图通过将指针作为参数,改变实参指针的值的函数调用

3.迭代时从i=0开始,i<=len导致多迭代一次的错误

4.遍布四处的内存泄露,注意它们的同时别忘了可能还有其他圈套……
 
排行榜 更多 +
善融商务

善融商务

购物比价 下载
给到 7.6.0

给到 7.6.0

购物比价 下载
简约费控 5.33.7

简约费控 5.33.7

系统软件 下载