当前位置: 首页 > news >正文

【c/c++】c语言的自增操作在不同编译器的差别

示例代码

代码如下:

#include <stdio.h>#define product(x) ((x)*(x))int main(void)
{int i = 3, j, k;j = product(i++);   // (i++) * (i++)k = product(++i);   // (++i) * (++i)printf("%d %d\n", j, k);
}

执行结果

在Ubuntu18.04下通过GCC编译和执行的结果:

在这里插入图片描述

注意第一个值是12。

在Windows10下通过VS2015编译和执行的结果:

在这里插入图片描述

注意第一个值是9。

也就是说同样的代码,在不同的编译器下执行的结果不同!

反汇编分析

通过汇编代码分析,首先查看GCC的反汇编:

jw@ubuntu:~/code/tmp$ gcc -g test.c
jw@ubuntu:~/code/tmp$ gdb a.out
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.out...done.
(gdb) l
1	#include<stdio.h>
2	
3	#define product(x) ((x)*(x))
4	
5	int main(void)
6	{
7	  int i = 3, j, k;
8	  j = product(i++);   // (i++) * (i++)
9	  k = product(++i);   // (++i) * (++i)
10	
(gdb) b 7
Breakpoint 1 at 0x652: file test.c, line 7.
(gdb) r
Starting program: /home/jw/code/tmp/a.out Breakpoint 1, main () at test.c:7
7	  int i = 3, j, k;
(gdb) set disassembly-flavor intel
(gdb) disas
Dump of assembler code for function main:0x000055555555464a <+0>:	push   rbp0x000055555555464b <+1>:	mov    rbp,rsp0x000055555555464e <+4>:	sub    rsp,0x10
=> 0x0000555555554652 <+8>:	mov    DWORD PTR [rbp-0xc],0x3		; 给i赋值为30x0000555555554659 <+15>:	mov    edx,DWORD PTR [rbp-0xc]	; 将i的值赋值给edx,所以edx = 30x000055555555465c <+18>:	lea    eax,[rdx+0x1]			; rdx的值就是edx的值,就是3,这里就是3 + 1赋值给eax,所以eax = 40x000055555555465f <+21>:	mov    DWORD PTR [rbp-0xc],eax	; 将eax的值赋值给i,也就是说,这里i发生了一次自增,此时i = 40x0000555555554662 <+24>:	mov    eax,DWORD PTR [rbp-0xc]	; 将i的值赋值给eax,所以eax = 40x0000555555554665 <+27>:	lea    ecx,[rax+0x1]			; rax的值就是eax的值,所以这里ecx = 50x0000555555554668 <+30>:	mov    DWORD PTR [rbp-0xc],ecx	; 将ecx的值赋值给i,这里又是一次i的自增,此时i = 50x000055555555466b <+33>:	imul   eax,edx					; 完成相乘的操作并赋值给eax,此时eax = 4, edx = 3, 所以eax = 120x000055555555466e <+36>:	mov    DWORD PTR [rbp-0x8],eax	; 将eax的值赋值给j,所以j = 12,到这里结果已经出来了0x0000555555554671 <+39>:	add    DWORD PTR [rbp-0xc],0x10x0000555555554675 <+43>:	add    DWORD PTR [rbp-0xc],0x10x0000555555554679 <+47>:	mov    eax,DWORD PTR [rbp-0xc]0x000055555555467c <+50>:	imul   eax,DWORD PTR [rbp-0xc]0x0000555555554680 <+54>:	mov    DWORD PTR [rbp-0x4],eax0x0000555555554683 <+57>:	mov    edx,DWORD PTR [rbp-0x4]0x0000555555554686 <+60>:	mov    eax,DWORD PTR [rbp-0x8]0x0000555555554689 <+63>:	mov    esi,eax0x000055555555468b <+65>:	lea    rdi,[rip+0xa2]        # 0x5555555547340x0000555555554692 <+72>:	mov    eax,0x00x0000555555554697 <+77>:	call   0x555555554520 <printf@plt>0x000055555555469c <+82>:	mov    eax,0x00x00005555555546a1 <+87>:	leave  0x00005555555546a2 <+88>:	ret    
---Type <return> to continue, or q <return> to quit---
End of assembler dump.
(gdb) q
A debugging session is active.Inferior 1 [process 20205] will be killed.Quit anyway? (y or n) y

查看注释的代码,可以看到j的值是如何计算出来的。

然后分析VS的反汇编:

00E73D8E  mov         dword ptr [i],3  		; i赋值为3j = product(i++);   // (i++) * (i++)
00E73D95  mov         eax,dword ptr [i] 	; eax = 3 
00E73D98  imul        eax,dword ptr [i]  	; eax = 9
00E73D9C  mov         dword ptr [j],eax  	; j = 9
00E73D9F  mov         ecx,dword ptr [i]  
00E73DA2  add         ecx,1  
00E73DA5  mov         dword ptr [i],ecx  	; 完成i的一次自增
00E73DA8  mov         edx,dword ptr [i]  
00E73DAB  add         edx,1  
00E73DAE  mov         dword ptr [i],edx  	; 再完成i的一次自增k = product(++i);   // (++i) * (++i)
00E73DB1  mov         eax,dword ptr [i]  
00E73DB4  add         eax,1  
00E73DB7  mov         dword ptr [i],eax  
00E73DBA  mov         ecx,dword ptr [i]  
00E73DBD  add         ecx,1  
00E73DC0  mov         dword ptr [i],ecx  
00E73DC3  mov         edx,dword ptr [i]  
00E73DC6  imul        edx,dword ptr [i]  
00E73DCA  mov         dword ptr [k],edx  

相比之下VS的汇编更直观,很容易就得到了9。

通过反汇编,可以看到两边到底是如何处理这个代码的,但是也仅此而已,只能证明两个编译器通过不同的方式执行了代码,但是具体哪一种是“对”的呢?汇编代码无法告诉我们。

进一步分析

从直觉上讲,问题应该是出在i++这个操作上,它是后置的自增操作,如果是简单的j = i++;,则GCC和VS的执行结果是一致的。

但是如果是i = i++;呢?将之前的代码修改:

#include <stdio.h>int main(void)
{int i = 3;i = i++;printf("%d\n", i);
}

查看GCC和VS的结果,发现确实也有差别,在GCC中的结果3,而VS中的结果是4!

从这里可以推出同一个变量在一行代码中存在执行顺序方面的不同可能,这跟编译器相关。通过查看c语言的标准(在Project status and milestones (open-std.org)可以查看c语言的标准,这里参考的是C11),里面有相关的说明:

在这里插入图片描述

也就是说c语言实际上将这种行为定义为undefined,所以不同的编译器就可以有不同的实现了。关于这个问题,在Stack Overflow有更多的说明,比如c - Why are these constructs using pre and post-increment undefined behavior? - Stack Overflow

总结

通常我们不会写i = i++;这样的代码,但是当有宏定义的时候,当它扩展之后还是存在未定义行为的情况,此时不同编译器实现可能导致不同的结果。

所以在使用++这种操作的时候需要小心,极端一点,甚至可以不用,实际上i++i = i + 1可能并没有什么差异,比如在VS里面通过反汇编查看两种情况时的汇编代码:

	int i = 3;
00BA1A6E  mov         dword ptr [i],3  i++;
00BA1A75  mov         eax,dword ptr [i]  
00BA1A78  add         eax,1  
00BA1A7B  mov         dword ptr [i],eax  

	int i = 3;
00C61A6E  mov         dword ptr [i],3  i = i + 1;
00C61A75  mov         eax,dword ptr [i]  
00C61A78  add         eax,1  
00C61A7B  mov         dword ptr [i],eax  

实际上的汇编代码根本没有区别(这里使用的是默认的编译参数,也许使用不同的优化参数会有不同结果)。

http://www.lryc.cn/news/3782.html

相关文章:

  • 【LeetCode第 332 场周赛】
  • 【蓝桥杯单片机】Keil5中怎么添加STC头文件;从烧录软件中添加显示添加成功后新建工程时依旧找不到
  • 图解浏览器渲染页面详细过程
  • 多线程面试题开胃菜1(5道)
  • 植物育种中广义遗传力的定义
  • 西瓜书读书笔记—绪论
  • ES8——Generator函数的使用
  • 德馨食品冲刺A股上市:计划募资9亿元,林志勇为实际控制人
  • 湿敏电阻的原理,结构,分类与应用总结
  • 千锋教育嵌入式物联网教程之系统编程篇学习-03
  • 升级到https
  • 【C语言】数据结构-二叉树
  • c++中std::condition_variable最全用法归纳
  • Python数据可视化:数据关系图表可视化
  • Urho3D约定
  • python数据结构-列表,元组
  • Properties类读配置文件、修改配置文件
  • 图解LeetCode——剑指 Offer 24. 反转链表
  • 【C语言】“指针的运算”、“指针与数组”
  • Linux高级命令之查找文件命令
  • PyCharm+Docker:打造最舒适的深度学习炼丹炉
  • 【mock】手把手带你用mock写自定义接口+mock常用语法
  • 2023 年腾讯云服务器CVM快速配置购买教程,新手上云必备!
  • opencv显示图像
  • C++:类和对象(中)
  • 53. 最大子数组和
  • 基于Java+SpringBoot+SpringCloud+Vue前后端分离医院管理系统设计与实现
  • QT基础入门【环境配置篇】linux桌面QT开发环境的构建以及问题解决
  • Linux系统之部署企业内部静态导航页
  • 2023备战金三银四,Python自动化软件测试面试宝典合集(四)