为什么 C/C++ 语言会有未定义行为?

C/C++本身存在着大量的未定义行为,可能很多人都被这样的包含未定义行为的书或者考试坑过。。。比如: printf("%d %d %d %d ", i…
关注者
255
被浏览
161,438

37 个回答

为了性能优化。

数组问题其他答案都说了,简单来说就是检查的开销甚至可能大于操作本身的开销。

我这边另一个举例就是大小写转换,char类型大小写转换只需要把第六位做位运算就行了,速度非常快,你可以使用avx来向量化,一个G的字符串单线程只需要0.1秒就转完。

但是他的代价就是你输入的东西不是字母A-Za-z的话就会有UB(实际上也不算UB,就是不正常的行为)。

那么我能不能先检查是不是字母,是的话位运算不是的话不做任何事呢?(嗯,其实STL的方法就是这么做的)

恭喜你,当你做这个检查以后,你很难把单线程1G字符串的大小写转换时间压进0.5s。而且用于涉及到多个条件判断(别忘了大Z和小a之间还特么有几个字符,需要4次大小比较),你需要使用AVX掩码操作,代码极其麻烦的同时,性能大幅下降。你要多倒腾好几个寄存器,多出3-6倍(取决于编译器优化)的AVX重指令。而不检查的方法只需要倒腾两个ZMM(其中一个是常量)、一个地址寄存器和一个计数变量。

同时印象中只有AVX512实现了复杂的掩码控制,在AVX2的时代,更麻烦。

在对性能极为敏感的应用(尤其是高性能计算行业)中,我们允许一定程度上的UB,允许一定程度上的不能正确handle异常,通过对用户的输入做出规范要求来获得性能提升。比如在我的那个应用中,如果用户得到了UB,我会拒绝他的bug修复申请并询问:为什么你的基因组测序序列里会有非字母?

当然我知道这种思路是有违互联网行业开发规范的,但是很不幸,高性能计算和互联网是两个行业,我们的客户是讲道理的,不会向你的程序中喂进去各种奇奇怪怪的东西,他们会欣然同意为了性能的考虑增加对输入的合理限制(比如说测序序列中本来就完全不应该出现非字母),他们只有一个要求:

慢是原罪。

主要原因:性能考虑,比如说一个简单例子,数组下标越界是ub,如果规定不是ub,而是一个固定的表现,比如抛异常,那么就像其他很多语言一样,每次操作时候检查,而且要知道,这个越界是针对数组而言的,比如你定义一个a[10],然后p=&a[5],那么p[6]一样是越界,要想catch住所有类似情况,就比较麻烦了,你是选择穷遍所有platform来选择具体方式(比如利用访问非法内存产生中断信号,但这其实也不100%保证,万一越界后的内存还是合法的呢),还是选择将p实现为一个伪指针对象?实际上正常程序是不会越界的,越界时候明确异常只是方便调试,所以如果你写一个C编译器,可以将debug和release分开实现。很多ub的确可以从语言设计上确定其行为,只不过这样一来是以性能天花板的降低为代价的,这跟其他语言比就没啥优势了,C++只是选择将责任扔给程序员,这类ub还有strict-aliasing之类的

其他一些原因:历史原因,平台差异等,其他人也说的差不多了