C语言 & Java 解析:当 i = i++ 后,结果究竟是什么?

Posted 一起学编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言 & Java 解析:当 i = i++ 后,结果究竟是什么?相关的知识,希望对你有一定的参考价值。

前言:

之前有朋友跟我讨论过一个问题,他问在java中,i=1;i=i++;之后的结果时什么,我一开始认为是2,但当他说答案是1的时候,我不得不想一个合理的解释,我想,可能是因为临时变量,当i赋值给i后,自增更改了临时变量,所以i值并没有改变。当我在为我的解释表示满意时,他们说在c语言下的答案是2,好吧,我只能说是编译器的实现不同,当然这个答案太没说服力了,所以寻找答案的任务就开始了。

我先在《c++ primer》上看见这样的解释:“++i的效率要比i++的效率高,因为i++必须要有一个临时变量来存放i本身的值“,那么我之前只说对了临时变量,但原因是临时变量先用来存放i的值,然后i本身自增,之后,临时变量赋值给左边,这时,i的值又被 自己之前的值覆盖了,所以又变成1,但这只是个推测,再说c语言中为什么是2?所以不得不反编译了。。。

java的jdk自带一个工具,javap,可以反编译,我们用它java编译器生成的“jvm版汇编码”,我会写下两段代码用来做对比,一种是i=i++;一种常见的j=i++;注意,我使用2作为初始值(也就是说它会被自增为3),因为1太特殊了,它在字节码中的变量名随处可见,而且++不是加1么,所以我用2做初值,也方便查找。

publicclassTest{

publicstaticvoidmain(String[] args){

inti =2;

  i = i++;

  System.out.println(i);

  }

}

javap -c Test 的字节码如下:

public class Test extends java.lang.Object{

public Test();

  Code:

  0: aload_0

  1: invokespecial #1; //Method java/lang/Object."<init>":()V

  4: return

public static void main(java.lang.String[]);

  Code:

  0: iconst_2

  1: istore_1

  2: iload_1

  3: iinc 1, 1

  6: istore_1

  7: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;

  10: iload_1

  11: invokevirtual #3; //Method java/io/PrintStream.println:(I)V

  14: return

}
0:  iconst_2  //将int型的常量2压栈

1:  istore_1  //将int型值存入变量1中

就是定义并初始化i的值。

关键是这里:

2:  iload_1  //保存变量1的值

3:  iinc  1, 1 //将变量1的值自增(i现在是3了)

6:  istore //将之前的保存值放入变量i中(i现在又变成2了。。)

其实在2: iload_1 时,i变量的值被放在了栈中,这就是我们说的临时存储。。之后,又被赋了过来

看看Test2,你就会明白正常的运作:

publicclassTest2{

publicstaticvoidmain(String[] args){

inti =2;

intj =0;

  j = i++;

  System.out.println(j);

  }

}

javap -c Test2 的字节码如下:

public class Test2 extends java.lang.Object{

public Test2();

  Code:

  0: aload_0

  1: invokespecial #1; //Method java/lang/Object."<init>":()V

  4: return

public static void main(java.lang.String[]);

  Code:

  0: iconst_2

  1: istore_1

  2: iconst_0

  3: istore_2

  4: iload_1

  5: iinc 1, 1

  8: istore_2

  9: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;

  12: iload_2

  13: invokevirtual #3; //Method java/io/PrintStream.println:(I)V

  16: return

}
4: iload_1  //变量1的值被保存(入栈)

5: iinc  1,1  //i自增

8 istore_2  // 弹栈,赋值给变量2.。。

现在明白了吧,,到底发生了什么。。

但是还没完,,,,,我们还没解释在c语言中发生了什么。。。

在gcc中,编译一个c程序要四个阶段,预处理,编译,汇编,链接。我们在编译阶段后停下,这样会得到AT&T的汇编码,

我们写下两种代码,类似于java版

先看正常情况:

#include<stdio.h>

intmain()

{

inti =2;

intj =0;

    j = i++;

printf("j = %d\\n",j);

return0;

}

编译成汇编码:

.file"test.c"

  .section .rodata

.LC0:

.string"j = %d\\n"

  .text

.globl main

.typemain, @function

main:

  pushl %ebp

  movl %esp, %ebp

  andl $-16, %esp

subl$32, %esp

movl$2, 28(%esp)

movl$0, 24(%esp)

  movl 28(%esp), %eax

  movl %eax, 24(%esp)

addl$1, 28(%esp)

  movl $.LC0, %eax

  movl 24(%esp), %edx

  movl %edx, 4(%esp)

  movl %eax, (%esp)

callprintf

movl$0, %eax

  leave

  ret

  .size main, .-main

.ident"GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"

.section.note.GNU-stack,"",@progbits

包括 movl $.LC0, %eax 和之后语句的都和输出有关了。

我们再把初始化有关的除去,关键的部分是:

movl    28(%esp), %eax

movl    %eax, 24(%esp)

addl$1, 28(%esp)

保存了i的值,(放在eax中),之后又放入j中,i再自增,这就很符合大家心里的答案,那么如果是i=i++时自然就是i被赋值,再自增。。。也就是大家心中的答案了,但是真是这样么?我们继续看。。。。

#include<stdio.h>

intmain()

{

inti =2;

i = i++;

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

return0;

}

对应的汇编码:

.file"test2.c"

  .section .rodata

.LC0:

.string"i = %d\\n"

  .text

.globl main

.typemain, @function

main:

  pushl %ebp

  movl %esp, %ebp

  andl $-16, %esp

subl$32, %esp

movl$2, 28(%esp)

addl$1, 28(%esp)

  movl $.LC0, %eax

  movl 28(%esp), %edx

  movl %edx, 4(%esp)

  movl %eax, (%esp)

callprintf

movl$0, %eax

  leave

  ret

  .size main, .-main

.ident"GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"

.section.note.GNU-stack,"",@progbits

关键部分就省一句了 addl $1, 28(%esp)这简直就是。。。。看我的test3.c吧。。。

#include<stdio.h>

intmain()

{

inti =2;

i++;

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

return0;

}

产生的汇编码:

aiqier@aiqier-laptop:~/c/test3$ cat test3.s

.file"test3.c"

  .section .rodata

.LC0:

.string"i = %d\\n"

  .text

.globl main

.typemain, @function

main:

  pushl %ebp

  movl %esp, %ebp

  andl $-16, %esp

subl$32, %esp

movl$2, 28(%esp)

addl$1, 28(%esp)

  movl $.LC0, %eax

  movl 28(%esp), %edx

  movl %edx, 4(%esp)

  movl %eax, (%esp)

callprintf

movl$0, %eax

  leave

  ret

  .size main, .-main

.ident"GCC: (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3"

.section.note.GNU-stack,"",@progbits

恩,在c语言中i=i++;居然跟i++;的汇编代码一样,按正常的逻辑(我们之前的分析),i=2;i=i++;i的值是3,已经没问题了,但是gcc会对编译器做一些优化,所以保存i的值,在赋值给i,这两条语句自然是鸡肋了,就被优化掉了。

总结,我们发现对于i++保存的临时值,java中是自增在返回临时值,而c语言中是返回临时值后,在自增,所以这就是为什么i=i++在这两种语言中不同的结果了,c#我就不知道了,有兴趣的同学可以试试,python么,没有++啦,呵呵,如果我的分析有什么不对之初,希望能与你一起探讨。

另外,对现在我们的大多数朋友来说还是学编程技术最重要!栽一棵树最好的时间是十年前,其次是现在。对于准备学习编程的小伙伴,如果你想更好的提升你的编程核心能力(内功)不妨从现在开始!

C语言C++编程学习交流圈子,QQ群:614504899点击进入】微信公众号:C语言编程学习基地

整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)

欢迎转行和学习编程的伙伴,利用更多的资料学习成长比自己琢磨更快哦!

以上是关于C语言 & Java 解析:当 i = i++ 后,结果究竟是什么?的主要内容,如果未能解决你的问题,请参考以下文章

c语言6-3

C语言编程:求n以内的k个最大素数以及他们的和,咋写?

请教2道C语言题目的编程思想

Java语言中,如何将String转化成Int数组

编写一个C语言程序:将一个数组中的值按逆序重新存放

c语言的两种排序?