关于相同代码在vs2017和Dev-c++中结果不同的原因初探索:UB(undefined behaviour)

关于相同代码在vs2017和Dev-c++中结果不同的原因初探索:UB(undefined behaviour)

今天一个学长拉着我研究了好一会儿一个简单的代码,因为它居然在不同软件中运行结果不同。这让我们有些费解,于是做了一些探究。
下面先附上代码(有问题)。

main()
{
    int i = 1;
    printf("%d,%d,%d\n",i = 3,i + 1, i);
}

下面分别是在Dev-c++和vs2017中运行的结果
dev-c gcc 4.92 x64
vs2017  x86

虽然创建的是cpp文件,但gcc环境并不影响c程序的运行。

那么问题来了,是不同软件编译器的问题吗?(优化全部关了)

于是先去查看两者的反汇编:

先是Dev-c++的

   0x0000000000401530 <+0>:	push   rbp
   0x0000000000401531 <+1>:	mov    rbp,rsp
   0x0000000000401534 <+4>:	sub    rsp,0x30
   0x0000000000401538 <+8>:	call   0x402100 <__main>
   0x000000000040153d <+13>:	mov    DWORD PTR [rbp-0x4],0x1
=> 0x0000000000401544 <+20>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000401547 <+23>:	lea    ecx,[rax+0x1]
   0x000000000040154a <+26>:	mov    DWORD PTR [rbp-0x4],0x3
   0x0000000000401551 <+33>:	mov    edx,DWORD PTR [rbp-0x4]
   0x0000000000401554 <+36>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000401557 <+39>:	mov    r9d,edx
   0x000000000040155a <+42>:	mov    r8d,ecx
   0x000000000040155d <+45>:	mov    edx,eax
   0x000000000040155f <+47>:	lea    rcx,[rip+0x2a9a]        # 0x404000
   0x0000000000401566 <+54>:	call   0x402b18 <printf>
   0x000000000040156b <+59>:	mov    eax,0x0
   0x0000000000401570 <+64>:	add    rsp,0x30
   0x0000000000401574 <+68>:	pop    rbp
   0x0000000000401575 <+69>:	ret 

然后是vs2017的

int main()
{
00007FF74E9A1820  push        rbp  
00007FF74E9A1822  push        rdi  
00007FF74E9A1823  sub         rsp,108h  
00007FF74E9A182A  lea         rbp,[rsp+20h]  
00007FF74E9A182F  mov         rdi,rsp  
00007FF74E9A1832  mov         ecx,42h  
00007FF74E9A1837  mov         eax,0CCCCCCCCh  
00007FF74E9A183C  rep stos    dword ptr [rdi]  
00007FF74E9A183E  lea         rcx,[__3C9E9496_test.cpp (07FF74E9B1003h)]  
00007FF74E9A1845  call        __CheckForDebuggerJustMyCode (07FF74E9A1082h)  
	int i = 1;
00007FF74E9A184A  mov         dword ptr [i],1  
	printf("%d,%d,%d\n", i = 3, i + 1, i);
00007FF74E9A1851  mov         dword ptr [i],3     
00007FF74E9A1858  mov         eax,dword ptr [i]  
 ***//dword 意为双字,即四字节,ptr即为指针,整行的意思为将内存地址i中的dword(32位)数据赋给寄存器eax***
00007FF74E9A185B  inc         eax  
00007FF74E9A185D  mov         r9d,dword ptr [i]  
00007FF74E9A1861  mov         r8d,eax  
00007FF74E9A1864  mov         edx,dword ptr [i]  
00007FF74E9A1867  lea         rcx,[string "%d,%d,%d\n" (07FF74E9A9C28h)]  
00007FF74E9A186E  call        printf (07FF74E9A11D1h)  
}
00007FF74E9A1873  xor         eax,eax  
}
00007FF74E9A1875  lea         rsp,[rbp+0E8h]  
00007FF74E9A187C  pop         rdi  
00007FF74E9A187D  pop         rbp  
00007FF74E9A187E  ret  

本人电子信息工程二年级生,还未接触过汇编语言。跟着学长大佬的脚步一起查找了相关汇编指令,勉强理解了反汇编码的大意并做了对比,发现两者的调用顺序并不一样。

Dev-c++中,遵循了c语言从右至左执行的原则。

在遇到不需要运算的表达式时,给出了相关变量的地址(即 i ),而遇到需要运算的表达式时,先运算,并开辟一个储存计算结果的临时内存,给出临时内存的地址(即 i + 1)。所以先执行i,把i的地址存入寄存器;然后计算 i + 1,得出2,把新的地址存在另一个寄存器中;再计算 i = 3,使i变为3,把i的地址存入寄存器;最后输出从左至右便为3,2,3.

而在vs2017中,却没有从右至左执行。

从反汇编中可看出,在vs中,是先执行了 i = 3,把i的地址存入寄存器;再执行了 i + 1,把新的地址存在另一个寄存器中;然后执行了i;最后一起输出:3,4,3.

那为什么执行顺序出现了不同呢,通过查询了相关资料和学长指导后了解了一个概念————UB(undefined behaviour),即未定义行为

以下仅为个人理解

那什么是未定义行为呢?
包含多个不确定的副作用的代码的行为总是被认为未定义。编译器对于未定义行为可能不会报错,但是这些行为编译器会自行处理。
这里附上一个比较全面生动的阐述c语言的未定义行为(undefined behaviour)

这里又涉及到了另外两个知识点:序列点副作用

1.在c语言中存在一些符号会产生序列点,例如“ ,” “&& 和 || ”等。这些序列点会对左右的表达式运算存在影响,而影响主要指的是副作用的生效顺序。
2.如果一个表达式不仅算出一个值,还修改了环境(数据对象或者文件),就说这个表达式有副作用(因为它多做了额外的事)。比如:a++ 。
3.可参考C语言中的序列点和副作用(文章里说:“甚至都不要试图探究这些东西在你的编译器中是如何实现的,K&R 明智地指出,”如果你不知道它们在不同的机器上如何实现, 这样的无知可能恰恰会有助于保护你。” 结果我正好去研究了。。)

#于是我大致得出了如下结论#

由于printf函数中存在带着副作用的表达式,导致计算机在执行printf过程中判定其为未定义行为,于是不同的编译器对相同的一段代码产生了自己的编译行为,导致了执行顺序不同,最后输出结果也不同了。

Final,在了解以上知识点外,深刻认识到了一点————在写程序的时候绝不能按照自己想当然的想法去写代码,这会是极大的隐患,所以我们应该尽量避免这种情况的发生,而是应该写出让所有编译器只会存在一种处理方式的代码

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值