鱼C论坛

 找回密码
 立即注册
查看: 7441|回复: 22

一个简单的问题关于++的

[复制链接]
发表于 2013-4-15 10:33:18 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
截图1365993093.png 截图1365993074.png
#include<stdio.h>
void main()
{
int i=5,j=5,p,q;
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j);
printf("%d,%d,%d,%d\n", p,q,i,j);
}
话说输出应该是5+6+7=18;6+7+8=21;8;8;
可是编译后居然是15;22;8;8
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-15 10:42:36 | 显示全部楼层
你在哪里看到这样的代码的 ?
小甲鱼最新课程 -> https://ilovefishc.com
 楼主| 发表于 2013-4-15 10:49:51 | 显示全部楼层
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-15 11:03:54 | 显示全部楼层
破翳の刃 发表于 2013-4-15 10:49
甲鱼哥的视频上,(006第二章 数据类型,运算符和表达式05)
能给个答案么?

题目是错误的

不同编译器给不同结果
类似
int i;
printf("%d", i );
i 没有值 然后直接输出
结果是不确定的
小甲鱼最新课程 -> https://ilovefishc.com
 楼主| 发表于 2013-4-15 11:07:52 | 显示全部楼层
我是师兄 发表于 2013-4-15 11:03
题目是错误的

不同编译器给不同结果

不是很理解,题目哪个地方错了呢,感觉要输出的东西都赋了值啊
望师兄解释
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-15 11:12:30 | 显示全部楼层
破翳の刃 发表于 2013-4-15 11:07
不是很理解,题目哪个地方错了呢,感觉要输出的东西都赋了值啊
望师兄解释

上网搜索关键字
C语言 未定义行为
C语言 序列点 副作用
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-15 11:15:30 | 显示全部楼层
这个你只要知道 ++i和i++的区别就行了
不同编程环境答案是不同的。

如果你写出类似
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j);
恭喜你 你离被炒鱿鱼不远了。

不要把时间浪费在无意义的地方。
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-15 11:16:21 | 显示全部楼层
这是我用vs2010编译得到的结果
QQ截图20130415111527.jpg
小甲鱼最新课程 -> https://ilovefishc.com
 楼主| 发表于 2013-4-15 19:18:20 | 显示全部楼层
好吧,谢谢各位,但是总觉得这个程序有一定的逻辑啊,按照运算法则可以得到答案的。为什么结果就这么2呢
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-16 21:18:28 | 显示全部楼层
我只是路过打酱油的。
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-16 22:13:32 | 显示全部楼层
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j);
i=5;j=5;
这里,先运算p=i+i+i+i=15;然后,i依次+1=8;
下一条:
先依次运行j+1直到j=8;q=8+8+8=24;
小甲鱼最新课程 -> https://ilovefishc.com
 楼主| 发表于 2013-4-16 22:19:39 | 显示全部楼层
俯首の低姿态 发表于 2013-4-16 22:13
p=(i++)+(i++)+(i++);
q=(++j)+(++j)+(++j);
i=5;j=5;

谢谢了,是编译器的问题吧?
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-16 22:23:59 | 显示全部楼层
破翳の刃 发表于 2013-4-16 22:19
谢谢了,是编译器的问题吧?

应该是。
  给你发个帖子:
这是一个很简单的程序
void main()
{
int i=8;
printf("%d,%d,%d,%d\n", ++i, --i, i++, i--);
}

在这里我使用了VS.NET2005编译的结果是:8,8,7,8
用TC2.0编译的结果是:8,7,7,8
VC6.0我没有安装,所以没有试过,也没办法分析。
这里我们可以看到,由不同的编译器产生了不同结果,可见这个问题是依赖编译器的理解和实现的。换句话说,对于i++和++i的处理本来就是非常具有歧义的,当然在自己应用中我相信也不会有任何程序员写出这样歧义的代码。但是作为一个问题,我们有必要分析一下不同编译器究竟如何理解i++和++i操作符的。
我们在学习C的时候,应该已经大概知道了i++和++i两者的区别,即“++”符号在i之前还是之后,决定了i自增操作和他的语句的执行顺序的关系。即i++,理解为i在其语句中取原始值,++i在其语句中取自增后的新值。这一点是毫无疑义的。但是问题在于,网友的问题中又涉及到了i++,++i在作为参数时候的处理,所以这时候我们就会感到困惑,i++和++i在作为参数的时候,和进入堆栈的顺序之间有何关系呢?根据前面的实验,可见TC2.0和VS.net2005的处理不同,可见两者对其处理不同,那么造成这种不同的结果的原因是什么呢?我们从代码上无法看到差异,因此我们必须看汇编语言才能知晓,编译器到底把我们的代码翻译成了什么墨阳。下面我采用IDA反汇编编译器把生成的.exe文件:由于程序很小,所以我们很容易找到相应的汇编代码(TC2.0的程序中有很多中断的相关代码,更加难以找到),结果如下:
VS.NET 2005的代码
mov [ebp+var_8], 8 //i=8
.text:004113E5 mov eax, [ebp+var_8] //
.text:004113E8 mov [ebp+var_D0], eax //i--之前把i的值保存到,temp[0]=8
.text:004113EE mov ecx, [ebp+var_8]
.text:004113F1 sub ecx, 1 //i--,(从右向左数第一个参数)
.text:004113F4 mov [ebp+var_8], ecx //i=7
.text:004113F7 mov edx, [ebp+var_8]
.text:004113FA mov [ebp+var_D4], edx //i++之前把i保存到:temp[1]=7 (这时候已经执行过i--了)
.text:00411400 mov eax, [ebp+var_8] //i++,(从右向左数第二个参数)
.text:00411403 add eax, 1
.text:00411406 mov [ebp+var_8], eax //i=8
.text:00411409 mov ecx, [ebp+var_8]
.text:0041140C sub ecx, 1 //--i, 无需保存修改前的值,直接改变实参i
.text:0041140F mov [ebp+var_8], ecx //i=7
.text:00411412 mov edx, [ebp+var_8]
.text:00411415 add edx, 1 //++i,
.text:00411418 mov [ebp+var_8], edx //i=8
.text:0041141B mov esi, esp
.text:0041141D mov eax, [ebp+var_D0] //压栈temp[0]=8
.text:00411423 push eax
.text:00411424 mov ecx, [ebp+var_D4] //压栈temp[1]=7
.text:0041142A push ecx
.text:0041142B mov edx, [ebp+var_8] //压栈i=8
.text:0041142E push edx
.text:0041142F mov eax, [ebp+var_8] //压栈i=8
.text:00411432 push eax
.text:00411433 push offset aDDDD ; "%d,%d,%d,%d\n" //压栈字符串"%d,%d,%d,%d\n"的地址
.text:00411438 call ds:printf //调用打印函数,输出8,8,7,8
.text:0041143E add esp, 14h
.text:00411441 cmp esi, esp
.text:00411443 call sub_41113B
.text:00411448 mov esi, esp
可见,++i和--i执行的时候直接改变了i的值,而i++和i--必须在所在的这个语句执行后才能改变i的值,所以i++作为参数时,实际上是这样的过程,
printf("%d",i++);
相当于下面的语句:
int temp=i;
i=(i+1);
printf("%d",temp);

因此上面的代码可以翻译为:
int i=8;
printf("%d,%d,%d,%d",++i,--i,i++,i--);

因此可以翻译为下面的等效代码:
i=8;
temp0=i; //temp0=8;
i--; //7
temp1=i; //temp1=7
i++; //8
--i; //7
++i; //i=8
printf("%d,%d,%d,%d",i,i,temp1,temp0);
所以打印结果是8,8,7,8

---------------------------------------------------------------------------------------------------------
我们再看在TC2.0下的反汇编代码:
TC2.0下面的反汇编代码
----------------------TC2.0反汇编结果--------------------
0:01FA sub_1FA proc near ; CODE XREF: start+11Ap
seg000:01FA push bp //保护bp入栈
seg000:01FB mov bp, sp
seg000:01FD push si //保护si入栈
seg000:01FE mov si, 8 //i=8
seg000:0201 mov ax, si //ax=i
seg000:0203 dec si //i--,i=7
seg000:0204 push ax //ax入栈,8入栈
seg000:0205 mov ax, si //ax=i,ax=7
seg000:0207 inc si //i++,i=8
seg000:0208 push ax //ax入栈,7入栈
seg000:0209 dec si //--i,i=7
seg000:020A mov ax, si //ax=i
seg000:020C push ax //ax入栈,7入栈
seg000:020D inc si //++i,i=8
seg000:020E mov ax, si //ax=i
seg000:0210 push ax //ax入栈,8入栈
seg000:0211 mov ax, 194h
seg000:0214 push ax //194h入栈
seg000:0215 call sub_AD9
seg000:0218 add sp, 0Ah
seg000:021B call sub_15DB
seg000:021E xor ax, ax
seg000:0220 jmp short $+2
seg000:0222 pop si //恢复si
seg000:0223 pop bp //恢复bp
seg000:0224 retn
seg000:0224 sub_1FA endp
可见,上面的行为可以翻译为下面的等效代码(TC2.0):
i=8;
temp0=i; //这时8已经入栈,实际上通过ax寄存器直接压栈里了~~~,不存在temp0)
i--; //i=7
temp1=i; //这时7已经入栈,实际上通过ax寄存器直接压栈里了~~~,不存在temp1)
i++; //i=8
--i; //i=7
temp2=i; //7已经入栈
++i; //i=8
temp3=i; //8已经入栈
printf("%d,%d,%d,%d",temp3,temp2,temp1,temp0);

输出结果是:8,7,7,8

----------------------------------------------------------------------------------------------------------------------
下面我们将总结和分析两种编译器的处理之间有何不同:@@@@@@

请注意两者的区别主要是,他们是一边处理自增自减并一边随时入栈,还是先处理完所有自增自减之后再最后统一一次性的入栈。
(1)在TC下面属于前者,每执行一个语句,就把i通过ax寄存器马上入栈了,所以参数入栈和i++等语句是交叉交替性进行的。这里的i++和++i的主要区别在于压栈是在i自增之前还是之后。
i++相当于:先入栈,再自增。
++i相当于,先自增,再入栈。
所以我们看到下面的参数:从右到左:
i--: 入栈8,i=7
i++:入栈7,i=8
--i:i=7,入栈7
++i:i=8,入栈8
所以导致栈里面的参数是8,7,7,8,所以打印结果是8,7,7,8.

(2)在上面的VC.net2005中属于后者,是先为i++和i--保存值,然后执行完所有的自增和自减,最后一次性的把所有参数入栈。在这里i++和++i的区别
主要是是否把i的值保存到另一个位置:而且最大不同点在于这里不马上入栈,而是等所有参数处理后统一入栈。
i++:先缓存i的原始值,然后i自增。最后入栈时,用i的原始值入栈。
++i:i自增,不缓存i的原始值。最后入栈时,是更新后的i。
所以我们看到在VC2005.NET中的顺序是:
i--: 缓存8,i=7
i++:缓存7,i=8
--i:i=7
++i:i=8
参数一次性依次入栈:第一个缓存值8,第二个缓存值7,i的当前值8,i的当前值8。
所以这时候栈的数据是:8,8,7,8.(从左到右)。
所以打印结果是:8,8,7,8.
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-18 16:51:41 | 显示全部楼层
疯了   我看到这么多的+  傻了
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-18 16:54:35 | 显示全部楼层
俯首の低姿态 发表于 2013-4-16 22:23
应该是。
  给你发个帖子:
这是一个很简单的程序

误导 该说的不说 不该说的说了一堆 而且还给出所谓的结果
另外 和入栈顺序无关
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-18 18:12:30 | 显示全部楼层
我是师兄 发表于 2013-4-18 16:54
误导 该说的不说 不该说的说了一堆 而且还给出所谓的结果
另外 和入栈顺序无关

你把该说的说一下,洗耳恭听
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-18 18:51:46 | 显示全部楼层
这种题  很少  不需太计较   !!!
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-18 19:08:56 | 显示全部楼层
这是编译器的一种优化方式 在i++前 已经把它加一了
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-18 19:13:07 | 显示全部楼层
嗯,楼上们说的都有道理!
小甲鱼最新课程 -> https://ilovefishc.com
发表于 2013-4-19 09:12:18 | 显示全部楼层
俯首の低姿态 发表于 2013-4-18 18:12
你把该说的说一下,洗耳恭听

该说的是 C语言中对未定义行为的定义
对求值顺序的描述
对副作用生效时机的描述

这些才是理解这道题应该掌握的知识点

小甲鱼最新课程 -> https://ilovefishc.com
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-8-3 03:21

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表