当前位置: 首页 > 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  

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

相关文章:

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

示例代码 代码如下&#xff1a; #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编译和执行的结果…...

【LeetCode第 332 场周赛】

传送门 文章目录6354. 找出数组的串联值6355. 统计公平数对的数目6356. 子字符串异或查询6357. 最少得分子序列6354. 找出数组的串联值 题目 思路 前后指针 代码 class Solution { public:long long findTheArrayConcVal(vector<int>& nums) {long long res 0;i…...

【蓝桥杯单片机】Keil5中怎么添加STC头文件;从烧录软件中添加显示添加成功后新建工程时依旧找不到

蓝桥杯单片机的芯片型号&#xff1a;IAP15F2K61S2 添加头文件&#xff1a;STC15F2K60S2.H 【1】如何通过烧录软件添加STC头文件&#xff1a; 从ATC-ISP的Keil仿真设置中添加&#xff08;同时自动下载仿真驱动&#xff09;仔细阅读添加说明 KEIL5添加STC芯片库_Initdev的博客-…...

图解浏览器渲染页面详细过程

渲染详细过程 产生渲染任务&#xff0c;开启渲染流程 当浏览器的网络线程收到 HTML 文档后&#xff0c;会产生一个渲染任务&#xff0c;并将其传递给渲染主线程的消息队列。 在事件循环机制的作用下&#xff0c;渲染主线程取出消息队列中的渲染任务&#xff0c;开启渲染流程。…...

多线程面试题开胃菜1(5道)

一.多线程有什么用&#xff1f;1&#xff09;发挥多核CPU 的优势随着工业的进步&#xff0c;现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的&#xff0c;4 核、8 核甚至 16 核的也都不少见&#xff0c;如果是单线程的程序&#xff0c;那么在双核 CPU 上就浪费了 50…...

植物育种中广义遗传力的定义

大家好&#xff0c; 我是邓飞。 今天聊一下广义遗传力的计算方法。 广义遗传力定义 广义遗传力&#xff08;H2H^2H2&#xff09;定义为归因于基因型总体遗传变异的表型变异比例。 通常他包括三个解释&#xff1a;&#xff08;详见我这篇博客的公式推导 回归系数 相关系数 遗…...

西瓜书读书笔记—绪论

文章目录机器学习典型的机器学习过程基本术语归纳偏好机器学习 机器学习&#xff1a;致力于研究如果通过计算的手段&#xff0c;利用经验来改善系统自身的性能 在计算机系统中&#xff0c;“经验” 通常以 “数据” 形式存在&#xff0c;因此&#xff0c;机器学习所研究的主要内…...

ES8——Generator函数的使用

babel工具插件下载&#xff1a;npm i --save babel-polyfill 引入&#xff1a;polyfill.js进行转码&#xff08;es8->es5&#xff09; 介绍 Generator函数用于生成迭代器 function * (){} yeild: 作用同return类似 {const obj function* () {yield "a";yield 12…...

德馨食品冲刺A股上市:计划募资9亿元,林志勇为实际控制人

近日&#xff0c;浙江德馨食品科技股份有限公司&#xff08;下称“德馨食品”或“德馨饮料”&#xff09;预披露更新招股书&#xff0c;准备在上海证券交易所主板上市。据贝多财经了解&#xff0c;德馨食品于2022年7月5日递交上市申请&#xff0c;安信证券为其保荐机构。 本次…...

湿敏电阻的原理,结构,分类与应用总结

🏡《总目录》 0,概述 湿敏电阻是指电阻值随着环境的湿度变化而变化的电阻,本文对其工作原理,结构,分类和应用场景进行总结。 1,工作原理 湿敏电阻是利用湿敏材料制成的,湿敏材料吸收空气中水分时,自身的阻值发生变化。 2,结构 如下图所示,市民电阻包括4个部分构成,…...

千锋教育嵌入式物联网教程之系统编程篇学习-03

目录 进程的终止 exit函数 _exit函数 进程退出清理 进程间的替换 进程间通信 常见通信机制 进程间通信的实质 信号 产生信号的方式 信号的默认处理方式 进程对信号的处理方式 kill函数 进程的终止 使用exit函数对进程进行终止&#xff0c;而return只是结束函数&a…...

升级到https

现在很多站长都会考虑将自己的站点从http升级到https&#xff0c;不仅是基于安全的考虑&#xff0c;有的也是因为第三方平台的限制&#xff0c;如谷歌浏览器会将http站点标记为不安全的站点&#xff0c;微信平台要求接入的微信小程序必须使用https等。 那如何将一个http站点升…...

【C语言】数据结构-二叉树

主页&#xff1a;114514的代码大冒险 qq:2188956112&#xff08;欢迎小伙伴呀hi✿(。◕ᴗ◕。)✿ &#xff09; Gitee&#xff1a;庄嘉豪 (zhuang-jiahaoxxx) - Gitee.com 引入 我们之前已经学过线性数据结构&#xff0c;今天我们将介绍非线性数据结构----树 树是一种非线性的…...

c++中std::condition_variable最全用法归纳

前言 建议阅读以下文章前需先对建立 std::thread 多线程与std::mutex 锁有一定程度的熟悉 std::thread最全用法归纳 std::mutex最全用法归纳 概括 使用 std::condition_variable 的 wait 会把目前的线程 thread 停下来并且等候事件通知&#xff0c;而在另一个线程中可以使用…...

Python数据可视化:数据关系图表可视化

目录 1、散点图 1.1、趋势显示的二维散点图 1.2、分布显示的二维散点图 1.3、散点曲线图...

Urho3D约定

Urho3D使用以下约定和原则&#xff1a; 左手坐标系。正X、Y和Z轴指向右侧、上方和前方&#xff0c;正旋转为顺时针。度用于角度。顺时针顶点定义正面。音频音量指定为0.0&#xff08;静音&#xff09;到1.0&#xff08;全音量&#xff09;路径名使用斜杠而不是反斜杠。调用操作…...

python数据结构-列表,元组

列表 列表是Python中最通用的数据类型&#xff0c;可以写成方括号之间的逗号分隔值(项目)列表。 使用列表的重要事项是&#xff0c;列表中的项目不必是相同的类型。也就是说一个列表中的项目(元素)可以是数字&#xff0c;字符串&#xff0c;数组&#xff0c;字典等甚至是列表类…...

Properties类读配置文件、修改配置文件

Properties类简介(1)Properties类是专门用于读写配置文件的集合类(2)配置文件的后缀名为.properties,内容格式为:# 可以用“#”作为注释 键值 键值**注意:**键值对不需要有空格,值不需要用引号一起来。默认类型是String。键、值不可以是null(3)Properties类的方法可查找api文档…...

图解LeetCode——剑指 Offer 24. 反转链表

一、题目 定义一个函数&#xff0c;输入一个链表的头节点&#xff0c;反转该链表并输出反转后链表的头节点。 二、示例 示例: 【输入】 1->2->3->4->5->NULL 【输出】 5->4->3->2->1->NULL 限制&#xff1a; 0 < 节点个数 < 5000 三、…...

【C语言】“指针的运算”、“指针与数组”

文章目录一、指针运算1.指针 - 整数2.指针-指针3.指针关系运算二、指针与数组三、二级指针四、指针数组完结一、指针运算 指针可以进行整数&#xff0c;指针-指针&#xff0c;还有关系运算&#xff0c;其他的运算会被编译器阻止。 1.指针 - 整数 对指针进行的时候一定要注意不…...

Linux高级命令之查找文件命令

查找文件命令学习目标能够说出查找文件使用的命令1. find命令及选项的使用命令说明find在指定目录下查找文件(包括目录)find命令选项:选项说明-name根据文件名(包括目录名)字查找find命令及选项的效果图:2. find命令结合通配符的使用通配符:是一种特殊语句&#xff0c;主要有星…...

PyCharm+Docker:打造最舒适的深度学习炼丹炉

九、PyCharmDocker&#xff1a;打造最舒适的深度学习炼丹炉 安装docker&#xff1a; 如何在 Ubuntu 22.04 LTS 中安装 Docker 和 Docker Compose https://zhuanlan.zhihu.com/p/547169542 修改Linux硬盘卷标&#xff1a; ntfs文件系统&#xff1a;https://blog.csdn.net/n…...

【mock】手把手带你用mock写自定义接口+mock常用语法

mock自定义接口完整流程 官网语法规范:https://github.com/nuysoft/Mock/wiki/Syntax-Specification 首先: 要有一个项目,我这里是vue3项目,以下从vue3项目搭建开始,已搭建好的请直接看2 1.空目录下新建vue3项目 运行创建项目命令&#xff1a; 在bash中:(文件路径处输入cm…...

2023 年腾讯云服务器CVM快速配置购买教程,新手上云必备!

腾讯云服务器快速配置购买教程是新手必备的上云教程。主机教程网在本文中以腾讯云服务器为例&#xff0c;给大家带来一个完整的、手把手教学的服务器购买流程。助力快速完成服务器的购买、配置、以及网站的搭建&#xff0c;给新手节省宝贵的时间&#xff0c;避免采坑&#xff0…...

opencv显示图像

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...

C++:类和对象(中)

文章目录1 类的6个默认成员函数2 构造函数2.1 概念2.2 特性3 析构函数3.1 概念3.2 特性4 拷贝构造函数4.1 概念4.2 特性5 赋值运算符重载5.1 运算符重载5.2 赋值运算符重载5.3 前置重载和后置重载6 日期类的实现7 const成员8 取地址及const取地址操作符重载1 类的6个默认成员函…...

53. 最大子数组和

文章目录题目描述暴力法动态规划法分治法参考文献题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组 是数组中的一个连续部分。 示例 1&#xff1a; 输入&…...

基于Java+SpringBoot+SpringCloud+Vue前后端分离医院管理系统设计与实现

博主介绍&#xff1a;✌全网粉丝3W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建、毕业项目实战、项目定制✌ 博主作品&#xff1a;《微服务实战》专栏是本人的实战经验总结&#xff0c;《S…...

QT基础入门【环境配置篇】linux桌面QT开发环境的构建以及问题解决

目录 一、下载QT的安装包 二、安装 1.执行以下命令开始安装 2.选择配置 三、启动...

Linux系统之部署企业内部静态导航页

Linux系统之部署企业内部静态导航页 一、本次实践目的二、检查本地系统环境1.检查系统版本2.检查内核版本三、下载静态导航页资源包1.创建下载目录2.下载资源包四、安装apache服务1.安装httpd2.复制网页文件3.重启httpd服务4.检查httpd服务状态五、访问导航页六、修改导航页网站…...

广告设计与制作学什么/网站优化服务

文章摘要&#xff1a;幼儿园的教育方式在逐步发生改变,以游戏贯穿幼儿教学已经成为当下幼儿教育的主流.在对幼儿实施游戏化教育中,我们对此进行了如下探索与研究.一、幼儿为什么要游戏化教学幼儿教学游戏化,是指把幼儿教育的目标、内容、要求融于各种游戏之中,教学游……幼儿教…...

网站建设一般报价/企业关键词优化最新报价

作者&#xff1a;闲鱼技术-君爱 1. 前言 闲鱼技术团队在2018年引入Flutter后&#xff0c;越来越多的业务场景在Flutter上使用。Flutter的亚秒级热重载一直是开发者的神兵利器&#xff0c;提供给开发者快速修改UI&#xff0c;增加功能&#xff0c;修复bug&#xff0c;不需要重新…...

wordpress中文版源码/seo咨询解决方案

目录 EFK架构&#xff08;elasticsearch\filebeat\kibana&#xff09; 1、下载elasticsearch、kibana、filebeat 2、创建用户并授权 3、安装并启动 3.1 使用elasticsearch账号安装启动 >3.1.1 解压 elasticsearch >3.1.2 配置 elasticsearch >3.1.3 启动elast…...

视频发布播放网站建设/中山网站seo

例21题目&#xff1a;猴子吃桃问题&#xff1a;猴子第一天摘下若干个桃子&#xff0c;当即吃了一半&#xff0c;还不瘾&#xff0c;又多吃了一个第二天早上又将剩下的桃子吃掉一半&#xff0c;又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想再吃时&a…...

做网站可能遇到的困难/优化营商环境条例解读

###转载于:https://www.cnblogs.com/tinaluo/p/9278671.html...

不用源码做网站/人教版优化设计电子书

【题目来源】https://www.acwing.com/problem/content/879/【问题描述】 给定 n 对正整数 ai,bi&#xff0c;对于每对数&#xff0c;求出一组 xi,yi&#xff0c;使其满足 aixibiyigcd(ai,bi)。【输入格式】 第一行包含整数 n。 接下来 n 行&#xff0c;每行包含两个整数 ai,bi。…...