++操作符汇编级别分析,各为看官必有所获。
今天重点讲下 ++ 和 – –,还有一点点C语言贪心法的东西。
希望大家今天也开心的学习哦。
1.++ 和 – 的使用对象
++ 和 – – 运算符都是单目运算符,其作用是用来对整型、字符型、指针型以及数组的元素等变量进行加1减1运算的,运算的结果仍是原类型,并存回原运算对象。
2.使用规则
首先++都可以在变量的前面和后面,被称为前置++和 后置++。
后置++ :一般我们叫先使用在++。
前置++ :一般我们叫先++在使用。
3.解读 ++(-- – 同理)
3.1 解读后置++
++ 到底是怎么进行呢?
看个例子:
int a = 10;int b = a++;printf("%d\n", b);printf("%d\n", a);
这结果很简单,不在分析,那我们看一点汇编吧。
可以分为以下几步:
1.对a进行初始化放入10.
首先,0Ah实际上就是10,mov可以理解为移动,就是把a的值放入dword ptr [a] 。
2.把a的值放入寄存器
然后,dword ptr [a] 的值 放到 eax寄存器中。
3.对b进行初始化
再把eax的值放入dword ptr [b]中。
4.对a的内容进行+1.
在把dword ptr [a] 的值放入ecx寄存器中,在把ecx中的值 加1 (就是add)
5.在返回到a空间
再把ecx的值放入dword ptr [a] 。
3.2解读后置++
那么前置呢?其实大同小异。
int a = 10;int b = ++a;printf("b = %d\n", b);printf("a = %d\n", a);
看下汇编,发现好像很相似。大概也分为下一下几步:
1.对a进行初始化放入10.
把a的值放入dword ptr [a] 。
2.把a的值放入寄存器
然后,dword ptr [a] 的值 放到 eax寄存器中。
3.对a进行加1(add)
把ecx中的值 加1
4.在返回到a空间
再把ecx的值放入dword ptr [a] 。
5.对b进行初始化
在把dword ptr [a] 的值放入ecx寄存器中,把ecx的值赋给dword ptr [b] 。
对比发现本质:
其实都把值移动到寄存器中在进行操作,后++就是先使用寄存器中的a值进行赋值操作,再对寄存器中的值进行+1,在返回a的空间;先++,那么就先在寄存器中进行+1操作后,返回a的空间,再使用寄存器中的值进行赋值。
3.3.如果不使用,单独的++什么意思呢?
首先,我们想个问题吧。
初始化和赋值一样吗?
后置++,叫先使用在++,那么不使用呢?
int a = 0; a = 10; a++;
看下汇编
赋值和初始化区别:
这里可以看出初始化和赋值不是一个操作。
首先初始化是把0的值移动到a的空间,
赋值则是把10再次移动到空间,直接覆盖。
后置++,不使用会直接++:
而a++好像跟上面分析的一样。只是没有赋值而已,所以没使用的话,是直接++。
4.++的复杂例子
举一个最常见例子
int i = 1;int a = (++i) + (++i) + (++i);printf("%d\n", a);
首先这样的代码,是非常不好的。
简单分析下:
这里汇编可以看出,是先把 i 加了3次1,(add了三次)。
在把这三次加1后的i值相加,最后把寄存器中的值赋值给a变量。
这里有个比较大的问题。
这个表达式执行顺序是怎么样呢?在所有编译器下结果一样吗?
在gcc下,结果是10,先执行前两个(++i)在把他们向+,在计算剩下的(++i),在把和进行计算,最后是10。
所以这是不通用的,这个表达式是很不好的。
4.贪心法
看个例子:
int i = 1;int a = (i++) + (i++) + (i++);
如果有人这样写代,刚好所有的括号忘带了,变成这样了
int i = 1;int a = i++ + i++ + i++;
会不会觉得抽象,但是结果可以可以打印出来。
那么下面的代码你会更抓狂的?
int i = 1;int a = i+++++i;
这是要干嘛?(一万个等号?)
这是想把i++和++i的值加起来,因为没带括号变成这个样子。
这样的东西就很抽象,你自己在后面加上合适的空格可以运行的。
int i = 1;int a = i++ + ++i;
但是,如果没有空格呢?编译器会咋办?
这是时编译器会采用贪心法,自动编译为
int i = 1;int a = i++++ +i;
它会把i++然后再++,再+i,这样明显是不行的,i++不可在被++,但是编译器为什么会这样做呢?
C 语言有这样一个规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。这个处理的策略被称为“贪心法”。需要注意到是,除了字符串与字符常量,符号的中间不能嵌有空白(空格、制表符、换行符等)。比如:==是单个符号,而= =是两个等号。
这就是编译器的贪心法,不会管对错,只是尽可能的解析为有意义的符号,比如上述的,它不会管i++不可在++,他只认为i++ ++是有意义的符号。
下面最后看个例子吧:
char a[] = "a\012b\018c";char *p = a;int count = 0;while (*p){printf("%c\n", *p);p++;count++;}printf("%d\n", count);;
这想打印的是一共有几个字符。
这里可以看出一共是6个字符。
用贪心法也很合理的解释了为什么,\01也是转义字符,为啥还要继续找的情况。
下期预告
下期说下 预处理指令
下期更精彩~~~
觉得有用的话给个三连把。