先打个广告欢迎加入编程<->逆向群: 90569473 ,本群无任何不良目的,欢迎加入!

《剑走偏峰来自一个中国人的C++教程》之三
C++的运算符与基本控制结构
  最近学校课程比较多,抽空还得去游戏里体验人生,所以更新的速度慢了些许。还如前一章所说的一样,本系列的核心思想在第一章,如果你懂了第一章中的思想,可以将这种思想推而广之,用则无穷,不用则如粪土!

   正如第一部分所说的,C++中提供了三种处理数据的方法运算符、基本控制结构、编程思想,本章讨论其中的两部分运算符与基本控制结构!  

在玩游戏的时候,我发现人生的确如游戏,当你在玩游戏的时候,不妨想象一样你自己,也许你只是地球游戏中被外星人创造的一个角色而已。这种想象原于对太极图的思考,在太极图中,阴中有阳圈,阳中有阴圈,个人觉得它的意思是在说,阴中有太极,阳中有太极,太极中有阴阳,即大太极套小太极,小太极套小小太极,依次无穷无尽,这个过程被老子称为天下大事必做于细,天下难事必做易!

class老子的话,object C++,现在让我们用这种套子思想来看待C++中的一些特性!
问题:有9个袋子,只有一个袋子存放9枚硬币,其它袋子都存放10枚硬币,给你一个天平,问最少称几次可以找出只有9枚硬币的袋子?
  
相信聪明的你,已经想到答案是2次,如你所想,我们首先将9个袋子平均分成三份,将每一份放入一个大袋子,然后任意取两个大袋子进行称量,如果相等则9枚硬币的袋子在没有称的大袋子中,相反则9枚硬币的袋子在较轻的大袋子中,然后从存放9枚硬币的大袋子中随便取出两个小袋子,同样的方法找到9枚硬币的小袋子!

  这个例子,我特意将分成3份的小袋子放入到了一个大袋子中,只是为了让人们理解天下大事必做于细,天下难事必做易这句话,用套子思想,返回去思考上面的问题,不难发现3个袋子(即为太极),大袋子(大太极),每个大袋子中的3个袋子(小太极)!

  这个抽象的过程是在说明天下所有的事物应该是有一些细小的基本元素组成的,是故我们称天下大事必做于细,天下难事必做于易,相信看到这里也许你会不得不承认我们祖先的伟大之处,这与某些现代面试中所说的细节决定成败如出一辙!

   继续以这种想法来讨论C++,以if分支结构为例,结合现实中的现象再举一个例子,在《走西口》这部电视剧中,主人公在从田家大院走到包头的过程与if分支结构基本相似!当主人公在路上发现饭店时,他可以根据需要进去吃或不吃(if结构,即可以执行也可以不执行),当主人公走到双叉路口时,他必须选择一条路走才能走到包头(if……else……结构,即必须执行一个分支),当主人公走到三叉路口时,他先选择一条路看能否继续走下去(if结构,可以走也可以不走),如果不能走,它需要分析剩下的两条路(if……else……结构必须执行一个分支,即必须选择一条路),可见,主人公面对三叉路口时为(if……if ……else结构),当主人公走到多个叉路口时,它需要一条条的判断能否走下去,即(if……else if……else if结构)!
  正如上面所说的过程,当你每天走在作任何事的路上时,不妨思考一下双叉路口、三叉路口,多叉路口,这时候你就会发现天下书本是用来查的,而决不是用来读的!
   
  正如我的教育观点一样,教育的作用是树人,而就小处来说人都做不好,岂能做大事,所以孔子提出了格物、致知、修身、齐家、治国、平天下的道理,这个道理也是遵守上面的天下大事必做于细道理的!
  
  这里写了这么多,还是在想完成我的目标让中国人认识自己的国人思想,并善于学习及利用!
  
一:位运算
  //阴阳学说与二进制(第一章中的例子)
#include "head.h"
class yinyang
{
  int yin;
  int yang;
public:
  yinyang(){yin=0;yang=1;}
  void and();
  void or();
  void not();
  void xor();
};

void yinyang::and()
{
  cout<<"阴阳相克,即0克1"<<endl;
  cout<<(yin&yang)<<endl;
}
void yinyang::or()
{
  cout<<"阴阳相克,即1克0"<<endl;
  cout<<(yin||yang)<<endl;
}
void yinyang::not()
{
  cout<<"阴阳相生,即0生1,1生0"<<endl;
  cout<<~yin<<" "<<~yang<<endl;
}
void yinyang::xor()
{
  cout<<"阴阳互制,即有一个1得1,有两个1得0"<<endl;
  cout<<(yin^yang)<<endl;
}  

int main()
  yinyang oneandzero;
  oneandzero.and();
  oneandzero.or();
  oneandzero.xor();
  oneandzero.not();
  return 0;
}
 上面的程序是在系列一中讨论阴阳学说与二进制的一个例子,在这个例子中就用到了位运算!
1.1:位运算及其机器编码
| & ~ ^ >> <<
机器编码:0x7c 0x26 0x7e 0x5e 0x3e 0x3c
1.2:汇编指令
 位运算是与汇编指令相对应的
and reg/mem,reg/mem/imm
or  reg/mem,reg/mem/imm
not reg/mem
xor reg/mem,reg/mem/imm
SAL/SAR Reg/Mem, CL/Imm
SHL/SHR Reg/Mem, CL/Imm
and操作是模为2的操作数的乘法操作,它的特点是遇0得0,该指令常用于mask操作,在前面提到过
or操作的特点是遇到1得1 
not操作是求反,它与neg指令的关系可以理解为neg=not+1 
xor操作是模为2的加法操作,它的特点是双1得0,正是因为模为2的加法操作,所以当1+1=2的时候,就相当于结果溢出了,于是取值为0,即1xor 1=0!
Sal/SAR是算术移位指令
SHL/SHR是逻辑移位指令!

1.3:C++中的位运算
 C++中的位运算特别需要注意>> <<,我们在输入输出时,常用到cout<<及cin>>这样的表达式,这是有运算符重载实现的,下面我们通过一个例子将C++中的cout<<修改为cout>>,以领会这种运算符重载的本质套子思想!
//示例程序
#define FAST
#include "head.h"
class test
{
  int a,b;
public:
  test();
  test(int a,int b);
  void operator>>(test &);
};

test::test()
{
  a=b=1;
}

test::test(int a,int b)
{
  this->a=a;
  this->b=b;
}

void test::operator >>(test &p)
{
  cout<<p.a<<" "<<p.b<<endl;
}

int main()
{
  test cout(1,2),b;
  cout>>b;
  return 0;
}
 我们建立了cout对象然后通过cout对象调用operator>>函数,这个函数的作用是输出b对象的内容!
  这个例子说明了C++运算符的一个特性重载,因为作为编程语言来说需要的是数据类型,面象对象解决了C++数据类型的问题,而与数据关系最为密切的是运算符,在面向对象后,运算符必须与数据相适应,所以C++提供了运算符的重载作用!

二:逻辑及关系运算符
2.1逻辑关系运算符及机器编码
&&
||
!

< > <= >= == !=
机器编码:0x3c 0x3d 0x3e  关系 <=>
0x7c 0x26 0x21 | & !
2.2:逻辑及关系运算符与汇编
 既然逻辑与关系与运算符的主要作用是为了描述控制结构中的条件,所以我们需要讨论一下IA32 CPU的状态寄存器与跳转指令:
标志寄存器
31 … 17 16 15 14 13 12 11 10  9   8  7   6  5 4  3   2  1     0 
… … VM RF    NT IOPL  OF DF  IF  TF SF ZF    AF     PF       CF 

运算结果标志位:
CF位主要用来反映运算是否产生进位或借位,如果运算结果的最高位产生了一个进位或借位,那么其值为1,否则其值为0,即有高位借位或者位则为真标识为1

PF用于反映运算结果中1的个数的奇偶性,如果1的个数为偶数则PF=1,否则为PF=0,即运算结果中1的个数为偶数则为真标识为1

AF位用在如下情况时:
在字操作时,发生低位字节向高位字节进位或借位时
在字节操作时,发生低4位向高4位进位或借位时

ZF位用来反映运算结果不否为0,如果为0则其值为1,否则为0,即运算结果是否为0,如果是则条件标识为真ZF=1

SF位用来反映运算结果的符号位,它与运算结果的最高位相同,运算结果为正数,则标识为0,否则为1,这与整数的符号编码是一样的,用0 1表示正负号,这是一种约定而已!

OF位用于反映运算结果是否溢出,如果溢出则OF=1,否则OF=0,即,运算是否溢出,如果溢出则条件为真,标志为1

状态标志位:
TF位,当TF=1时,CPU进入单步执行方式,即每执行一条指令,产生一个单步中断请求!这种方式主要用于程序的调试,指令系统中没有专门的指令来改变标志位TF的值,但可以通过pushfd和popfd来曲线设置

IF位,用来决定CPU是否响应CPU外部的可屏蔽中断发送的中断请求,具体规定如下:
IF=1,CPU可以CPU外部的可屏蔽中断发出的中断请求
IF=0,CPU不响应CPU外部的可屏蔽中断发出的中断请求
  CPU指令系统中有专门的指令来改变标志位的IF值!

DF位,用来决定串操作指令时有关指针寄存器发生调整的方向!

32位标志寄存器增加的标志位
IOPL用两位2进制位来表示,该字段指定了要执行I/O指令的特权级,如果当前的特权级在数值上小于等于IOPL的值,那么,该I/O指令可执行,否则将发生一个保护异常!

NT位,用来控制中断返回指令IRET的执行,具体规定如下:
NT=0,用堆栈中保存的值恢复EFLAGS CS EIP,执行常规的中断返回操作
NT=1,通过任务转换实现中断返回

RF位,重启动RF用来控制是否接受高度故障,规定:RF=0时,表示接受调试故障,否则拒绝之,在成功执行完一条指令后,处理机把RF置为0,当接受到一个非调试故障时,处理机就把它置为1!

VM=1表示CPU在8086方式下工作,VM=1表示处理机在保护方式下工作,在Windows开机时,实际上就是通过这个标志位实现CPU实模式到保护模式的切换的

跳转指令:
jmp

无符号跳转
JE/JZ  ZF=1 Jump Equal or Jump Zero 
JNE/JNZ ZF=0 Jump Not Equal or Jump Not Zero 
JA/JNBE CF=0 and ZF=0 Jump Above or Jump Not Below or Equal 
JAE/JNB CF=0 Jump Above or Equal or Jump Not Below 
JB/JNAE CF=1 Jump Below or Jump Not Above or Equal 
JBE/JNA CF=1 or AF=1 Jump Below or Equal or Jump Not Above 

有符号跳转
JE/JZ  ZF=1 Jump Equal or Jump Zero 
JNE/JNZ ZF=0 Jump Not Equal or Jump Not Zero 
JG/JNLE ZF=0 and SF=OF Jump Greater or Jump Not Less or Equal 
JGE/JNL SF=OF Jump Greater or Equal or Jump Not Less 
JL/JNGE SF≠OF Jump Less or Jump Not Greater or Equal 
JLE/JNG ZF=1 or SF≠OF Jump Less or Equal or Jump Not Greater 

特殊跳转指令
JC  CF=1 Jump Carry 
JNC  CF=0 Jump Not Carry 
JO  OF=1 Jump Overflow 
JNO  OF=0 Jump Not Overflow 
JP/JPE  PF=1 Jump Parity or Jump Parity Even 
JNP/JPO  PF=0 Jump Not Parity or Jump Parity Odd 
JS  SF=1 Jump Sign (negative) 
JNS  SF=0 Jump No Sign (positive)

2.3:C++中的逻辑与关系与运算符
  在C++中运算符+操作数称为表达式,对于关系与逻辑运算表达式,都有一个bool值的结果,因此我们能连用关系及逻辑运算符,比如下面的语句:
  int a,b,c;
  if(a>b>c)...........;
  if(a<b<c)............;
  a>b>c,根据结合性先计算a>b的值,如果是真则为1,如果是假则为0,所以如果a=4,b=3,c=2的话,实际上不是在比较4>3>2而是在比较1>2,正确的描述为
a>  b&&b>c
//示例程序
#define FAST  
#include "head.h"

int main()
{
  int a=1,b=0;
  cout<<(a&&b)<<endl; //位关系位逻辑,<<符号是C++进行重载后的,在操作符重载后其优先级不变,故需要加括号改变&& ||运算符在表达式中的优先级
  cout<<(a||b)<<endl;
  cout<<!a<<endl;

  return 0;
}


//a&&b主要的反汇编代码
00401296    837D FC 00        cmp dword ptr ss:[ebp-4],0                ; 设置标志位
0040129A    74 0F             je short api.004012AB
0040129C    837D F8 00        cmp dword ptr ss:[ebp-8],0
004012A0    74 09             je short api.004012AB
004012A2    C745 F4 01000000  mov dword ptr ss:[ebp-C],1
004012A9    EB 07             jmp short api.004012B2
004012AB    C745 F4 00000000  mov dword ptr ss:[ebp-C],0
  
在上面的代码不难看出在a&&b表达式的运算结果保存到了[ebp-C]中,这也验证了我们开头说的话,关系与逻辑表达式都会有一个BOOL值的结果!正是因为逻辑与运算的作用是判断操作中是否有0,所以a&&b表达式的反汇编指令是cmp与je指令配合,通过与0相减,如果结果为0则操作数为0,ZF=1通过je指令跳转最为合适!

//a||b主要的反汇编代码
004012E2    837D FC 00        cmp dword ptr ss:[ebp-4],0
004012E6    75 0F             jnz short api.004012F7
004012E8    837D F8 00        cmp dword ptr ss:[ebp-8],0
004012EC    75 09             jnz short api.004012F7
004012EE    C745 F0 00000000  mov dword ptr ss:[ebp-10],0
004012F5    EB 07             jmp short api.004012FE
004012F7    C745 F0 01000000  mov dword ptr ss:[ebp-10],1

与a&&b相同a||b表达式也有一个值,这个bool值保存到了[ebp-10]中,正因为逻辑或运算的作用是判断操作数中是否有1,所以a||b表达式的反汇编指令是cmp与jne指令配合,通过与0相减,如果结果不为0,则操作数中一定有一个不为0,ZF=0,通过jne指令最为合适!
//!a反汇编
0040133A    837D FC 00        cmp dword ptr ss:[ebp-4],0
0040133E    0F94C2            sete dl
 与上面与或逻辑表达式一样,!操作也会保存运算结果,这个值保存到了EDX中,所以才会有下面的反汇编代码:
0040133E    0F94C2            sete dl
00401341    8BFC              mov edi,esp
00401343    52                push edx
00401344    8B0D DC504100     mov ecx,dword ptr ds:[<&MSVCP60D.std::cou>; MSVCP60D.std::cout

sete指令是设置非EFLAGS寄存器状态标志位的一个指令,它的作用是如果ZF=1则dl=0,如果zf=0则dl=1!
//示例关系与swtich结构
#define FAST
#include "head.h"

int main()
{
  int a,b;
  cin>>a>>b;
  int c;
  a>b?c=1:c=2;
  switch(c)
  {
  case 1:
    cout<<"big!"<<endl;
    break;
  case 2:
    cout<<"little!"<<endl;
    break;
  default:
    cout<<"equal!"<<endl;
    break;
  }
  return 0;
}


//程序反汇编详解
00401270 >  55                push ebp          ;保存前一个栈帧的栈底
00401271    8BEC              mov ebp,esp        ;开辟一个新的栈帧
00401273    83EC 54           sub esp,54        ;分配栈空间
00401276    53                push ebx          ;保存ebx
00401277    56                push esi          ;保存esi
00401278    57                push edi          ;保存edi 实际上就是context结构的一部分,在面向对象中push ecx总是最后,用于指示对象
00401279    8D7D AC           lea edi,dword ptr ss:[ebp-54]    ;将栈空间的首地末地址保存到edi中
0040127C    B9 15000000       mov ecx,15        ;设置栈空间的初始化次数 0x54=84/4=21=0x15,这里的次数与分配的栈空间是一致的
00401281    B8 CCCCCCCC       mov eax,CCCCCCCC        ;栈空间初始化的值0xccccccccc,实际上就是int3,这个int3与int 3不一样,后者为0xcc03
00401286    F3:AB             rep stos dword ptr es:[edi]    ;重复串操作将eax的值保存到开辟的栈空间中,实现栈空间初始化
00401288    8BF4              mov esi,esp        ;esi=esp
0040128A    8D45 F8           lea eax,dword ptr ss:[ebp-8]              ; 得到变量b的地址
0040128D    50                push eax                                  ; 进栈准备做参数
0040128E    8BFC              mov edi,esp
00401290    8D4D FC           lea ecx,dword ptr ss:[ebp-4]              ; 得到变量a的地址
00401293    51                push ecx                                  ; 入栈准备作参数
00401294    8B0D F4504100     mov ecx,dword ptr ds:[<&MSVCP60D.std::cin>; 标准输入到变量a中
0040129A    FF15 F0504100     call dword ptr ds:[<&MSVCP60D.std::basic_>; 调用操作符重载函数
004012A0    3BFC              cmp edi,esp
004012A2    E8 7B010000       call api._chkesp                          ; 检测栈平衡
004012A7    8BC8              mov ecx,eax
004012A9    FF15 F0504100     call dword ptr ds:[<&MSVCP60D.std::basic_>; MSVCP60D.std::basic_istream<char,std::char_traits<char> >::operator>>
004012AF    3BF4              cmp esi,esp
004012B1    E8 6C010000       call api._chkesp                          ; jmp 到 MSVCRT._chkesp
004012B6    8B55 FC           mov edx,dword ptr ss:[ebp-4]              ; a的值入edx
004012B9    3B55 F8           cmp edx,dword ptr ss:[ebp-8]              ; 比较a与b的值
004012BC    7E 0F             jle short api.004012CD                    ; 小于则跳转即大于时不跳
004012BE    C745 F4 01000000  mov dword ptr ss:[ebp-C],1                ; 大于时则c=1
004012C5    8B45 F4           mov eax,dword ptr ss:[ebp-C]              ; c的值保存到EAX中
004012C8    8945 F0           mov dword ptr ss:[ebp-10],eax             ; 保存a>b表达式的值到ebp-0x10中
004012CB    EB 0D             jmp short api.004012DA                    ; 跳入switch结构
004012CD    C745 F4 02000000  mov dword ptr ss:[ebp-C],2                ; a<b时c=2
004012D4    8B4D F4           mov ecx,dword ptr ss:[ebp-C]              ; c的值保存到ecx中
004012D7    894D F0           mov dword ptr ss:[ebp-10],ecx             ; 保存a>b表达式的值
004012DA    8B55 F4           mov edx,dword ptr ss:[ebp-C]              ; switch结构开始处
004012DD    8955 EC           mov dword ptr ss:[ebp-14],edx
004012E0    837D EC 01        cmp dword ptr ss:[ebp-14],1               ; case 1:
004012E4    74 08             je short api.004012EE                     ; 进入case 1:分支
004012E6    837D EC 02        cmp dword ptr ss:[ebp-14],2               ; case 2:
004012EA    74 39             je short api.00401325                     ; 进入case 2:分支
004012EC    EB 6E             jmp short api.0040135C                    ; 跳转到default分支中
004012EE    8BF4              mov esi,esp                               ; case 1:分支处的指令开始处
004012F0    A1 EC504100       mov eax,dword ptr ds:[<&MSVCP60D.std::end>
004012F5    50                push eax
004012F6    8BFC              mov edi,esp
004012F8    68 30304100       push api.00413030                         ; ASCII "big!"
004012FD    8B0D E8504100     mov ecx,dword ptr ds:[<&MSVCP60D.std::cou>; MSVCP60D.std::cout
00401303    51                push ecx
00401304    FF15 E4504100     call dword ptr ds:[<&MSVCP60D.std::operat>; MSVCP60D.std::operator<<
0040130A    83C4 08           add esp,8
0040130D    3BFC              cmp edi,esp
0040130F    E8 0E010000       call api._chkesp                          ; jmp 到 MSVCRT._chkesp
00401314    8BC8              mov ecx,eax
00401316    FF15 E0504100     call dword ptr ds:[<&MSVCP60D.std::basic_>; MSVCP60D.std::basic_ostream<char,std::char_traits<char> >::operator<<
0040131C    3BF4              cmp esi,esp
0040131E    E8 FF000000       call api._chkesp                          ; jmp 到 MSVCRT._chkesp
00401323    EB 6D             jmp short api.00401392                    ; case 1分支结束处,跳转到程序结束
00401325    8BF4              mov esi,esp                               ; case 2:分支的指令开始处
00401327    8B15 EC504100     mov edx,dword ptr ds:[<&MSVCP60D.std::end>; MSVCP60D.std::endl
0040132D    52                push edx
0040132E    8BFC              mov edi,esp
00401330    68 24304100       push api.00413024                         ; ASCII "little!"
00401335    A1 E8504100       mov eax,dword ptr ds:[<&MSVCP60D.std::cou>
0040133A    50                push eax
0040133B    FF15 E4504100     call dword ptr ds:[<&MSVCP60D.std::operat>; MSVCP60D.std::operator<<
00401341    83C4 08           add esp,8
00401344    3BFC              cmp edi,esp
00401346    E8 D7000000       call api._chkesp                          ; jmp 到 MSVCRT._chkesp
0040134B    8BC8              mov ecx,eax
0040134D    FF15 E0504100     call dword ptr ds:[<&MSVCP60D.std::basic_>; MSVCP60D.std::basic_ostream<char,std::char_traits<char> >::operator<<
00401353    3BF4              cmp esi,esp
00401355    E8 C8000000       call api._chkesp                          ; jmp 到 MSVCRT._chkesp
0040135A    EB 36             jmp short api.00401392                    ; case 2:分支指令结束处,跳转到程序结束
0040135C    8BF4              mov esi,esp                               ; default语句开始处
0040135E    8B0D EC504100     mov ecx,dword ptr ds:[<&MSVCP60D.std::end>; MSVCP60D.std::endl
00401364    51                push ecx
00401365    8BFC              mov edi,esp
00401367    68 1C304100       push api.0041301C                         ; ASCII "equal!"
0040136C    8B15 E8504100     mov edx,dword ptr ds:[<&MSVCP60D.std::cou>; MSVCP60D.std::cout
00401372    52                push edx
00401373    FF15 E4504100     call dword ptr ds:[<&MSVCP60D.std::operat>; MSVCP60D.std::operator<<
00401379    83C4 08           add esp,8
0040137C    3BFC              cmp edi,esp
0040137E    E8 9F000000       call api._chkesp                          ; jmp 到 MSVCRT._chkesp
00401383    8BC8              mov ecx,eax
00401385    FF15 E0504100     call dword ptr ds:[<&MSVCP60D.std::basic_>; MSVCP60D.std::basic_ostream<char,std::char_traits<char> >::operator<<
0040138B    3BF4              cmp esi,esp
0040138D    E8 90000000       call api._chkesp                          ; jmp 到 MSVCRT._chkesp
00401392    33C0              xor eax,eax                               ; return 0; 
通过反汇编这个程序,我们得到如下结论:
A:再次证明关系表达式是要保存结果的,在上面的反汇编代码中,我们可以看到每个case 分支附近都有表达式值的保存!
B:熟悉了switch结构的反汇编,在逆向C++的时候,很难分清循环与简单分支结构的不同,实际上,循环结构的反汇编标志就是回跳,而简单分支结构的特点是向下的跳转,这并不是一个难把握的东西!
三:算术运算符
  算术运算符可简单的分为如下几类:
1:+ - 正负号运算
2:+ - * / % 
3: 增量运算++ --
4:=赋值运算
1.1:算术运算符的机器编码
  + - * / % =
     0x2b 0x2d 0x2a 0x2f 0x25  0x3d
    
1.2:算术运算符与汇编指令
add reg/mem,reg/mem/imm  源操作数+目的操作数,结果保存到目的操作数中
adc reg/mem,reg/mem/imm  源操作数+目的操作+CF,结果保存到目的操作数中
inc reg/mem  操作数+1
xadd reg/mem,reg 目的操作数与源操作数切换位置,然后再相加,使用该指令必须保证有一个操作数位于寄存器中,这牵涉到寻址与CPU周期的问题,作为CPU来说,无法直接实现内存到内存的数据流动!

//示例
mov dword ptr ss:[ebp-4],2
mov eax,2
add eax,dword ptr ss:[ebp-4] ;eax=4
adc eax,dword ptr ss:[ebp-4] ;cf=1 eax=7 cf=0 eax=6
inc eax ;eax=7 eax=8
xadd dword ptr ss:[ebp-4],eax ;eax=2 ss:[ebp-4]=9 or A


sub reg/mem,reg/mem/imm 从目的操作数减去源操作数
sbb reg/mem,reg/mem/imm 从目的操作数减去CF值后再减去源操作数
dec reg/mem 操作数-1
neg reg/mem 求补,即求相反数,在计算机中的表示需要按照一定的规则
 neg指令对应于负号运算符

mul reg/mem 将操作数与EAX的值相乘并将其值保存到EDX和EAX中
//示例
mov dword ptr ss:[ebp-4],5
mov eax,2
mul dword ptr ss:[ebp-4]
 些时EDX的值为0,EAX的值为A

imul reg1,reg2/mem 386+ reg1*reg2->reg1 或 reg1*mem->reg1

//示例
imul eax,dword ptr ss:[ebp-4] ;eax=0x32

div reg/mem 将操作数与EDX-EAX相除,商保存到eax,余数保存到edx中
idiv reg/mem 与div操作是一样的

div dword ptr ss:[ebp-4] ;eax=0xA
idiv dword ptr ss:[ebp-4] ;eax=0x2
idiv dword ptr ss:[ebp-4] ;eax=0 edx=2
1.3:c++中的算术运算符
//示例程序
#define FAST
#include "head.h"

int main()
{
  int a=3;
  while(--a)
  {
    cout<<a<<endl;
  }

  cout<<"restarting......."<<endl;
  a=3;
  while(a--)
  {
    cout<<a<<endl;
  }
  return 0;
}
00401288    C745 FC 03000000  mov dword ptr ss:[ebp-4],3                ; int a=3;
0040128F    8B45 FC           mov eax,dword ptr ss:[ebp-4]              ; eax=3
00401292    83E8 01           sub eax,1                                 ; eax=2
00401295    8945 FC           mov dword ptr ss:[ebp-4],eax              ; a=2
00401298    837D FC 00        cmp dword ptr ss:[ebp-4],0                ; 2与0进行比较
0040129C    74 33             je short api.004012D1

.........................
00401306 >  C745 FC 03000000  mov dword ptr ss:[ebp-4],3    ;a=3
0040130D    8B55 FC           mov edx,dword ptr ss:[ebp-4]    ;edx=3
00401310    8B45 FC           mov eax,dword ptr ss:[ebp-4]    ;eax=3
00401313    83E8 01           sub eax,1          ;eax=2
00401316    8945 FC           mov dword ptr ss:[ebp-4],eax    ;a=2
00401319    85D2              test edx,edx        ;3与3进行比较
0040131B    74 33             je short api.$E30        ;分支
.............................
00401328    8B55 FC           mov edx,dword ptr ss:[ebp-4]     ;edx=2
0040132B    52                push edx          ;edx入栈
0040132C    8B0D E4504100     mov ecx,dword ptr ds:[<&MSVCP60D.std::cou>; MSVCP60D.std::cout ;运算符重载输出edx的值



 不难看出前算术运算,是先减1,然后用减去的结果去进行操作,而后算术运算是将操作数保存到两个寄存器中,然后将其中之一的寄存器减1,而后再用另一个寄存器去作为条件进行判断,然后再将条件判断的寄存器内容进行修改,通过这个过程来判断while(--a)比while(a--)少一次条件判断!

所以上面程序的结果是2 1 2 1 0 

结论:前增量运算要比后运算的指令少好几条,所以在编程时最好使用前增量!同时后增量的值会紧接着在条件判断后对数据进行增量操作!


//理解赋值运算符
#define FAST
#include "head.h"

int main()
{
  int a,b,c,d;
  a=1;
b=2;
  c=3;
  d=4;
  /*
00401288    C745 FC 01000000  mov dword ptr ss:[ebp-4],1                          
0040128F    C745 F8 02000000  mov dword ptr ss:[ebp-8],2
00401296    C745 F4 03000000  mov dword ptr ss:[ebp-C],3
0040129D    C745 F0 04000000  mov dword ptr ss:[ebp-10],4


  */
  cout<<"restarting.......!"<<endl;

  a=b=c=d=1;
  /*
004012D9    C745 F0 01000000  mov dword ptr ss:[ebp-10],1
004012E0    8B55 F0           mov edx,dword ptr ss:[ebp-10]
004012E3    8955 F4           mov dword ptr ss:[ebp-C],edx
004012E6    8B45 F4           mov eax,dword ptr ss:[ebp-C]
004012E9    8945 F8           mov dword ptr ss:[ebp-8],eax
004012EC    8B4D F8           mov ecx,dword ptr ss:[ebp-8]
004012EF    894D FC           mov dword ptr ss:[ebp-4],ec
  */
  return 0;
}

 通过上面的代码不难看出,右结合性连续赋值在执行上并不效率,只不过在编写源码时我们方便了一些而已,实际上在2个变量的连续赋值中是可行的,而越多则越不效率,越不如单句赋值效率!赋值运算关键记住左右二字,左必须为左值,右为右结合性!

//区分=与==
#define FAST
#include "head.h"

int main()
{
  int i;
  if(i=42)
  {
    cout<<"此处检测i值是否为0"<<endl;
  }

  if(i==42)
  {
  cout<<"此处检测i值是否为42"<<endl;
  }
  return 0;
}


//反汇编代码 if(i=42)
00401288    C745 FC 2A000000  mov dword ptr ss:[ebp-4],2A                                 
0040128F    837D FC 00        cmp dword ptr ss:[ebp-4],0 
00401293    74 35             je short api.004012CA
.....................
//反汇编if(i==42)
004012CA    837D FC 2A        cmp dword ptr ss:[ebp-4],2A
004012CE    75 35             jnz short api.00401305
  第一个if结构的作用是先将i赋值为42,然后检测i的值是否为0,不为0则条件为真,而后一个是检测i的值是否等于42,如果是则为真!

四:特殊运算符
 逗号运算符,是从左到右计算机表达式的值,将最后一个表达式的值作为整个逗号表达式的值!
//,运算符示例
#define FAST
#include "head.h"

int main()

  int a,b;
  if(a=0,b=1)
  {
    cout<<"1"<<endl;
  }

  if(b=1,a=0)
  {
    cout<<"0"<<endl;
  }
  return 0;
}

//关键反汇编
00401288    C745 FC 00000000  mov dword ptr ss:[ebp-4],0                                   ; esi=esp
0040128F    C745 F8 01000000  mov dword ptr ss:[ebp-8],1
00401296    837D F8 00        cmp dword ptr ss:[ebp-8],0
0040129A    74 35             je short api.004012D1                                    
........................
004012D1    C745 F8 01000000  mov dword ptr ss:[ebp-8],1
004012D8 >  C745 FC 00000000  mov dword ptr ss:[ebp-4],0
004012DF    837D FC 00        cmp dword ptr ss:[ebp-4],0
004012E3    74 35             je short api.0040131A
->运算符,这是一个方便对象指针的运算符
//示例
#define FAST
#include "head.h"

class test
{
  int a;
public:
  void show(){cout<<a<<endl;}
  test(){a=1;}
};

int main()
{
  test a,*p;
  p=&a;
  p->show();
  (*p).show();
  a.show();
  return 0;
}

五:太极图与C++运算符、基本控制结构
1:太极图中的套子思想C++运算符的重载特性与if结构
2:太极图中的S曲线程序执行流程!
  在太极图中阴阳分界的是S曲线,这条线为什么是S曲线而不是像象棋棋谱中那样为直线简洁而明显呢?
  纵观世间万物,只有相对的暂时笔直,决无永远的笔直,比如你看到的路、山川、河流无一笔直的事物,再比如地球,在空中看为圆,地球上看为方,程序的执行流程也一样,表面上看是顺序执行的,但是实际上是有许多条件分支组成的,将S曲线求极限(limS曲线),S曲线的组成部分越接近直线,这与程序执行流程也基本类似,我们的程序写的越小则分支越少,越接近于纯顺序执行!
3:正如人生一样,道路之曲折处,恰是生死福祸之变化处,程序也一样,分支执行处,恰时破解产生之时!
4:思考题
A:为什么本章从hex编码、汇编指令去讨论C++的运算符?
B:为什么位与运算是&而逻辑与运算为&&?