由于未定义的行为,许多不好的事情发生并且继续发生(或者不知道,谁知道,任何事情都可能发生).据我所知,这是为了让编译器进行优化留下一些摆动空间,也可能使C++更容易移植到不同的平台和架构.然而,由未定义的行为引起的问题似乎太大而无法通过这些论证来证明.未定义行为的其他参数是什么?如果没有,为什么还存在未定义的行为?
编辑为我的问题添加一些动机:由于使用较少C++的几个糟糕经历 - 狡猾的同事,我已经习惯了使我的代码尽可能安全.断言每一个论点,严谨的正确性以及类似的东西.我试图离开,因为小房间可能以错误的方式使用我的代码,因为经验表明,如果有漏洞,人们会使用它们,然后他们会打电话给我,说我的代码很糟糕.我认为让我的代码尽可能安全是一种好的做法.这就是为什么我不明白为什么存在未定义的行为.有人可以给我一个未定义行为的例子,这些行为在运行时或编译时无法检测到而没有相当大的开销吗?
我对未定义行为的看法是这样的:
该标准定义了语言的使用方式,以及在以正确方式使用时应该如何做出反应.但是,要涵盖每个功能的每个可能用途都需要做很多工作,所以标准就是这样做.
但是,在编译器实现中,您不能只是"保留它",代码必须转换为机器指令,并且您不能只留下空白点.在许多情况下,编译器可能会抛出错误,但这并不总是可行的:在某些情况下需要额外的工作来检查程序员是否做错了(例如:调用析构函数两次 - 来检测这个,编译器必须计算已调用某些函数的次数,或添加额外的状态或其他内容).因此,如果标准没有定义它,并且编译器只是让它发生,那么有时可能会发生诙谐的事情,如果你不幸的话.
我认为关注的核心来自于C/C++的速度哲学.
这些语言是在原始功耗稀疏的情况下创建的,您需要获得所有优化才能获得可用的功能.
指定如何处理UB意味着首先检测它,然后当然指定适当的处理.然而,检测它是违反语言的速度第一哲学!
今天,我们还需要快速的节目吗?是的,对于我们这些使用非常有限的资源(嵌入式系统)或非常严格的约束(响应时间或每秒事务数)工作的人来说,我们确实需要尽可能地挤出来.
我知道这个座右铭会在这个问题上投入更多硬件.我有一个应用程序,我工作:
预计回答的时间?不到100毫秒,中间有数据库调用(感谢memcached).
每秒交易次数?平均1200,峰值1500/1700.
它运行在大约40个怪物上:8个双核opteron(2800MHz)和32GB RAM.此时使用更多硬件变得"更快"变得困难,因此我们需要优化代码和允许它的语言(我们确实限制在那里抛出汇编代码).
我必须说,无论如何我对UB并不在意.如果你达到程序调用UB的程度那么它需要修复实际发生的任何行为.当然,如果立即报告它将更容易修复它们:这就是调试版本的用途.
所以也许我们应该学会使用这种语言,而不是专注于UB:
不要使用未经检查的电话
(对于专家)不要使用未经检查的电话
(对于大师)你确定你真的需要一个未经检查的电话吗?
一切都突然变得更好:)
问题不是由未定义的行为引起的,它们是由编写导致它的代码引起的.答案很简单 - 不要写那种代码 - 不这样做并不完全是火箭科学.
至于:
在运行时或编译时无法检测到的未定义行为的示例,没有相当大的开销
现实世界的问题:
int * p = new int; // call loads of stuff which may create an alias to p called q delete p; // call more stuff, somewhere in which you do: delete q;
在编译时检测到这一点是不可能的.在运行时它只是非常困难,并且需要内存分配系统进行更多的簿记(即更慢并占用更多内存),而不是简单地说第二次删除是未定义的.如果您不喜欢这样,也许C++不是您的语言 - 为什么不切换到java?
未定义行为的主要来源是指针,这就是为什么C和C++有很多未定义的行为.
考虑以下代码:
char * r = 0x012345ff; std::cout << r;
这段代码看起来很糟糕,但它应该发出错误吗?如果该地址确实可读,即它是我以某种方式获得的值(可能是设备地址等),该怎么办?
在这种情况下,没有办法知道操作是否合法,如果不合法,它的行为确实是不可预测的.
除此之外:一般来说,C++在设计时考虑了"零开销规则"(参见C++的设计和演变),因此它不可能对实现检查角落情况等施加任何负担.您应该始终保持请记住,这种语言的设计确实不仅适用于桌面,也适用于资源有限的嵌入式系统.