首页
社区
课程
招聘
[原创]剑走偏峰——来自一个中国人的C++教程(系列三)
发表于: 2010-3-23 19:11 12228

[原创]剑走偏峰——来自一个中国人的C++教程(系列三)

2010-3-23 19:11
12228

先打个广告——欢迎加入编程<->逆向群: 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:为什么位与运算是&而逻辑与运算为&&?


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (17)
雪    币: 217
活跃值: (25)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
支持楼主。详细
2010-3-23 20:19
0
雪    币: 214
活跃值: (21)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
太好 了 很详细的
有助于知识的进一步深化
2010-3-25 18:08
0
雪    币: 7
活跃值: (333)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
4
欣赏LZ-----把易经、知识、人生道理三者紧密结合。
2010-3-25 18:16
0
雪    币: 204
活跃值: (33)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
强烈建议把doc文本传上来
2010-3-28 16:53
0
雪    币: 52
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
tor
6
收下来,回来好像阅读!谢谢LZ
2010-3-30 13:40
0
雪    币: 81
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
C++的概念比较复杂,经你这么一说,更加复杂了.文章充斥着画蛇添足的味道,要宣扬你的哲学观请另开一帖.
2010-4-1 16:08
0
雪    币: 273
活跃值: (64)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
8
呵呵,楼上说的是,不过声明一点,那不是我的哲学观,同时看雪也不是哲学论坛!
2010-4-1 17:55
0
雪    币: 81
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
你的教程已经很深入很详细了,个人感觉在加上诸如"阴阳"这样的概念,会混淆初学者的神经系统.对于"哲学观"的问题,I'm sorry.
2010-4-1 18:41
0
雪    币: 273
活跃值: (64)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
10
恩恩,你说的有道理,但是我想,对于人来说,有意义的知识恰好是那些你无法接受或不懂的知识,对于一个想学习的人,这一定不是困难所在!
2010-4-1 21:42
0
雪    币: 486
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
不管怎样,能学到知识就是好的!
2010-4-4 19:09
0
雪    币: 207
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
强烈要求楼主出chm版本
2010-4-5 23:38
0
雪    币: 349
活跃值: (32)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
楼主太高深了  楼主内心一定很强大
2010-4-24 22:53
0
雪    币: 9
活跃值: (142)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
14
研究太极的???????
2010-4-25 00:08
0
雪    币: 12
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
厉害 楼主强啊 看来我得来学习学习了
2010-4-25 00:11
0
雪    币: 144
活跃值: (25)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
16
支持了!哈哈!支持TCG!
2010-5-1 17:48
0
雪    币: 4
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
遗憾,我看不懂!
2010-5-1 18:02
0
雪    币: 1301
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
18
很不错的教学文章,写的真好,长了见识,收藏了。
2010-5-6 15:05
0
游客
登录 | 注册 方可回帖
返回
//