首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

关于i++与++i背后的一些引发思考的有关问题

2013-01-11 
关于i++与++i背后的一些引发思考的问题看论坛上有人对i++和++i之类的话题表示很反感,认为太简单了,可是真

关于i++与++i背后的一些引发思考的问题
看论坛上有人对i++和++i之类的话题表示很反感,认为太简单了,可是真的简单么?


首先上一段简单代码


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


首先看gcc反汇编代码


004013BA  |.  C745 FC 00000>MOV DWORD PTR SS:[EBP-4],0               ;将i赋值为0
004013C1  |.  8B45 FC       MOV EAX,DWORD PTR SS:[EBP-4]             ;将i的值放到寄存器eax中
004013C4  |.  894424 04     MOV DWORD PTR SS:[ESP+4],EAX             ;将eax的值放到栈中,注意,这就是入栈的过程,printf("..",i);中i的出处
004013C8  |.  8D45 FC       LEA EAX,DWORD PTR SS:[EBP-4]             ;将i的地址赋值给寄存器eax 
004013CB  |.  FF00          INC DWORD PTR DS:[EAX]                   ;将i的值自增1
004013CD  |.  C70424 000044>MOV DWORD PTR SS:[ESP],Untitled.00440000 ;printf的参数"%d\n"
004013D4  |.  E8 B7F20000   CALL <JMP.&msvcrt.printf>                ;调用printf函数


从这上面的代码中可以看出,gcc里面的工作方式并不是一些书里面说的先调用printf函数,然后再将i的值自增1,而是用eax将i的值先入栈,然后将i自增,然后在调用printf


004013D9  |.  C745 FC 00000>MOV DWORD PTR SS:[EBP-4],0               ;将i赋值为0
004013E0  |.  8D45 FC       LEA EAX,DWORD PTR SS:[EBP-4]             ;将i的地址赋值给eax
004013E3  |.  FF00          INC DWORD PTR DS:[EAX]                   ;将i自增1
004013E5  |.  8B45 FC       MOV EAX,DWORD PTR SS:[EBP-4]             ;将i的值赋值给eax
004013E8  |.  894424 04     MOV DWORD PTR SS:[ESP+4],EAX             ;将eax的值入栈,同上i的出处
004013EC  |.  C70424 000044>MOV DWORD PTR SS:[ESP],Untitled.00440000 ;printf的参数"\d\n"
004013F3  |.  E8 98F20000   CALL <JMP.&msvcrt.printf>                ;调用


这里面的代码和平常理解的差不多,先自增,然后再调用i

然后看下vs(debug版)的反汇编代码

004113BE    C745 F8 0000000>MOV DWORD PTR SS:[EBP-8],0;将i的值赋值为0
004113C5    8B45 F8         MOV EAX,DWORD PTR SS:[EBP-8];将i的值放到eax中
004113C8    8985 30FFFFFF   MOV DWORD PTR SS:[EBP-D0],EAX;保存eax,非i
004113CE    8B4D F8         MOV ECX,DWORD PTR SS:[EBP-8];用ecx保存i的值


004113D1    83C1 01         ADD ECX,1;ecx的值自增1
004113D4    894D F8         MOV DWORD PTR SS:[EBP-8],ECX;然后将ecx的值赋值给i
004113D7    8BF4            MOV ESI,ESP;用esi保存栈顶指针
004113D9    8B95 30FFFFFF   MOV EDX,DWORD PTR SS:[EBP-D0];将保存的eax的数的值赋值给edx
004113DF    52              PUSH EDX;入栈edx
004113E0    68 3C574100     PUSH tmp.0041573C                        ;"%d\n"
004113E5    FF15 BC824100   CALL DWORD PTR DS:[<&MSVCR90D.printf>]   ;调用printf
004113EB    83C4 08         ADD ESP,8;_cdecl调用约定,调用完成后恢复堆栈平衡
004113EE    3BF4            CMP ESI,ESP;比较esi和esp的值是否相同
004113F0    E8 50FDFFFF     CALL tmp.00411145;检查堆栈是否平衡,函数名为CheckEsp



关键处的代码都差不多,只是vs对堆栈平衡方面有更好的维护。


004113F5    C745 F8 0000000>MOV DWORD PTR SS:[EBP-8],0
004113FC    8B45 F8         MOV EAX,DWORD PTR SS:[EBP-8]
004113FF    83C0 01         ADD EAX,1
00411402    8945 F8         MOV DWORD PTR SS:[EBP-8],EAX
00411405    8BF4            MOV ESI,ESP
00411407    8B4D F8         MOV ECX,DWORD PTR SS:[EBP-8]
0041140A    51              PUSH ECX
0041140B    68 3C574100     PUSH tmp.0041573C                        ; ASCII "%d
"
00411410    FF15 BC824100   CALL DWORD PTR DS:[<&MSVCR90D.printf>]   ; MSVCR90D.printf
00411416    83C4 08         ADD ESP,8


同样和平时理解的一样,注释部分就不给出了,大家有兴趣可以看下release版的代码有什么区别,我就不给出了。

上面从细节方面说i++与++i的区别,下面说一下我碰到一些和i++,++i有关的问题。

看这一段代码


int i = 0;

DWORD ThreadProc(LPVOID pParam)
{
    i++;
    return 0;
}

int main()
{
    for(int tmp = 0; tmp < 100; tmp++)
    {
        HANDLE handle = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ThreadProc,0,0,NULL);
        CloseHandle(handle);
    }
    cout << i << endl;
    system("pause");


}



运行之后,发现i的值最后居然等于86,而不是100,难道是循环的次数不够?那不可能,可能的原因就是出在i++上面的,观察最上面的源代码,会发现,一条小小的i++居然有那

么多条汇编指令,而线程的执行是无序的,在没有进行同步的情况下,当CPU时间片轮转完之后,会切换到另外一个线程,从而执行另外一个线程的那些汇编指令,如果有个线程的

inc eax后切换到另外一个线程的mov eax,0 ,那inc eax这个操作就白做了,这就是得出86的缘故。

再看一段代码
写了一个比较大小的宏定义,不用模板就可以比较各种类型,挺好


#define MAX(a,b) ((a) >= (b) ? (a) : (b))

int main()
{
int a = 10;
int b = 5;
printf("较大的值是:%d\n",MAX(a++,b));
system("pause"); 
}


运行之后发现返回的较大的值居然是11,什么原因?自己找(*^__^*) 

既然i++与++i有不同,那么它们俩效率怎么样呢?做个试验。


int num = 0x3fffffff;
int i = 0;
    clock_t begin;
    clock_t end;
    begin = clock();
    while(num--)
    {
        i++;
    }
    end = clock();
    printf("i++所用的时间是:%d\n",end - begin);
    num = 0x3fffffff;
    begin = clock();
    while(num--)
    {
        ++i;
    }
    end = clock();
    printf("++i所用的时间是:%d\n",end - begin);


运行这段代码,编译器vs2008,debug
得出几组结果

i++所用的时间是:4047
++i所用的时间是:3890

i++所用的时间是:3984
++i所用的时间是:3954

i++所用的时间是:3953
++i所用的时间是:3938

i++所用的时间是:3969
++i所用的时间是:3812

i++所用的时间是:3844
++i所用的时间是:3750

i++所用的时间是:3875
++i所用的时间是:3750

i++所用的时间是:3828
++i所用的时间是:3719

i++所用的时间是:3828
++i所用的时间是:3735

编译器vs2008,release
运行多次,结果均为下面的结果,至于为什么,自己找,嘿嘿
i++所用的时间是:0
++i所用的时间是:0
网上说++i效率比i++高的同学,做release这步研究了么?区别也没有说说的那么大,毕竟给别人运行的是release版。

再看下面一个例子

int i = 0;
    printf("i的字节数为:%d\n",sizeof(++i));
    printf("i的值为:%d\n",i);
    system("pause");

运行的结果如下
i的字节数为:4
i的值为:0
请按任意键继续. . .

然后就有疑问了,为什么i的值不是1呢?难道是++i没执行?自己研究找结果。

写这个帖子只是为了说明一个简单的例子都能引发很多思考,或许就在不经意间忽略了,而且你怎么调试都调试不出来,最后发现了是某某很简单的问题,最后恍然大悟。
本人菜鸟,高手勿喷。
[解决办法]
关于效率, 
对于int这种类型, 编译器优化下几乎很少差别.

要是map::iterator之类的迭代器 , 前后++差别会很大吧.
[解决办法]
    int b = 5;
    printf("较大的值是:%d\n",MAX(a++,b));
    system("pause"); 

未定义行为 不同编译器结果不一样的
[解决办法]
。。。
看来还是早期的书说的对,++,--,尽量单独一个语句,这样就无所谓前后了,至于说前置效率高,实际开了优化基本差不多

[解决办法]
简单的原则,
1. ++i, i++都写成单独的语句,除非你能100%确定不会有副作用。
2. 不要用++i和i++做函数参数。因为有些函数可能实际上是个宏定义。
比如
#define max(x, y) ((x) > (y)) ? (x) : (y)
max(i++, j++)的实际情执行的语句是

((x++) > (y++)) ? (x++) : (y++)
这个结果相信不是max(i++, j++)想要的。
------解决方案--------------------


引用:
简单的原则,
1. ++i, i++都写成单独的语句,除非你能100%确定不会有副作用。
2. 不要用++i和i++做函数参数。因为有些函数可能实际上是个宏定义。
比如
#define max(x, y) ((x) > (y)) ? (x) : (y)
max(i++, j++)的实际情执行的语句是

((x++) > (y++)) ? (x++) : (y……

这情况下应当是宏的设计有问题。宏的设计意图就是看起来像函数调用。如果实际行为不同于函数调用的话,这是宏的问题而不是i++的问题。

热点排行