UB未定义行为
前言
额。。惭愧,今天在群里听到UB,我还以为是啥咋没听过。
UB:undefined behaviour
,简称:未定义行为
。
不过u1s1啊。undefined,这个经常见,所以一听就知道,组合词确实是我孤陋寡闻了。
正文
这是当时群里的题
不过借此,也可以适当进一步了解未定义行为。
首先,未定义行为怎么来的?也不合适,应该是先考虑定义。
所谓的语言标准,自然有自己的标准委员会提出,也就是c++ iso的说法,即c++11、c++14巴拉巴拉的,除去这一层,不同的编译器也可能自己做了优化,又多了一层定义。那么这层定义就比较搞了,因为相同的代码在不同编译器下就很有可能出问题。所以尽量不要再这上面钻牛角尖。
未定义行为:可以说是语言标准没有规定,编译器自行决定的行为,而且在不同的编译器上有时会有不同的结果。
例一
就是很无聊的++问题
1 | int i = 0; |
在我们的印象里,后置递增,先操作后自增。即i=i; i++
但是还有一种恶心的情况,i++; i=i
当然这里能想到后者就完犊子了,肯定是先赋值后递增,所以i=1
然后就来了
1 | int i = 0; |
问?正常情况下你觉得是i=(i++)+j
还是i=i+(++j)
让我们瞅瞅vs的反汇编
1 | 00007FF72C6A1E22 mov eax,dword ptr [j] |
汇编指令inc 是让操作数+1
首先把j的值传给eax,把i的值传给ecx,然后ecx+eax,再把ecx的值传给eax,也就是i+j,
然后把eax的值传回给变量i,变量i的值又传给eax,然后eax 完成inc,也就是自增,最后再把eax的值传回变量i。也就是后面的自增。
显然在vs中,i = (i++)+j
。
tips:这里其实也有个小坑,就是运算符优先级,他会优先处理掉高等级的,所以在有的时候你不要去让编译器考虑优先级,你应该加括号自己先预演好优先级
还有一种吊炸天的问题
1 | int i = 0; |
你说是i+ (++(++j))
还是i++ + ++j
很无厘头啊,虽然现在的vs会要求你有格式控制,一整串他是不认账的。
但是你要放在试卷上,就是铁nt行为。
像已知的,gcc、msvc、clang他们的支持就有区别了。
这种无厘头的说实在没有必要太纠结,开发这么写要给打死。
也别只拿vs的反汇编说话,虽然能给出一个答案,但是不管从什么角度而言这种题目都很反人类。
例二
标准定义,常量是在初始化也可以说定义后是不能被修改的
1 | const int a = 1; |
那么a的值?
在iso硬性规定常量不能被修改,因为常量被当成右值了。
当我们强制转换成整型指针然后取他的地址。就变相的控制了
很显然这也是未定义行为,不过有些时候还真有这么用的,但是不太好。毕竟万一常量释放了,这个指针又不知道搞哪去了。
例三
就是重复释放
1 | int *p = new int; |
编译器不会报错,但不能保证会不会出现别的结果。
总而言之
我所知道的都是比较浅薄的,百度一搜都有很多高端的东西,像是容器+算法然后整的活。可能这才是真正的程序员吧,还要翻阅文献什么的确实太顶了。。
严格意义上,未定义行为不算一个详细的知识体系,你不可能记住所有的未定义行为吧。。。毕竟是叫未定义啊,有些指不定还是你整的活。
常见的反正就是那么几种
- 有符号类型赋值的时候超出范围
- 函数体之间忘记初始化之类的
- 算术表达式写的不好就会产生未定义的结果
- 数学性质除以0
- 经典溢出,有的时候溢出是不一定会报错的
结语
坦白的说,还不够深入了解
但是一码归一码,像这种未定义行为可能还是因为c/c++比较自由
还得努力学啊~~~感觉自己就是废物