读书人

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

发布时间: 2013-01-11 11:57:35 作者: rapoo

关于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++的问题。

读书人网 >C++

热点推荐