C ++中不稳定错误的最常见原因是啥?

Posted

技术标签:

【中文标题】C ++中不稳定错误的最常见原因是啥?【英文标题】:Most common reasons for unstable bugs in C++?C ++中不稳定错误的最常见原因是什么? 【发布时间】:2009-08-28 12:02:37 【问题描述】:

我目前正在处理一个大型项目,并且大部分时间都在调试。虽然调试是一个正常的过程,但存在一些不稳定的错误,这些错误是开发人员最大的痛苦。该程序不起作用,嗯,有时...有时会,但您无能为力。

如何解决这些错误?最常见的调试工具(交互式调试器、监视、日志消息)可能会让您无处可去,因为错误会消失……只是稍后会再次出现。这就是为什么我要求一些启发式方法:此类错误的最常见原因是什么?我们应该调查哪些可疑代码来定位此类错误?

让我开始列表:

    使用未初始化的变量。 常见的印刷错误,例如 mMember = 会员; 线程同步。 有时这可能是一个问题 运气; 使用非智能 指针,取消引用无效 那些;

还有什么?

【问题讨论】:

【参考方案1】:

IME 在许多项目中的根本问题是开发人员使用 C++ 的低级功能,例如手动内存管理、C 风格的字符串处理等,尽管它们很少需要(然后只能很好地封装在类中) .这会导致内存损坏、无效指针、缓冲区溢出、资源泄漏等等。一直以来,都可以使用漂亮而干净的高级构造。

多年来,我一直是大型(多个 MLoC)应用程序团队的一员,应用程序不同部分的崩溃错误数量与这些部分中使用的编程风格密切相关。当被问及他们为什么不改变他们的编程风格时,一些罪魁祸首回答说他们的风格通常会产生更高的性能。 (这不仅是错误的,这也是一个事实,即客户宁愿拥有一个更稳定但速度较慢的程序,而不是一个不断崩溃的快速程序。此外,他们的大部分代码甚至不需要快速......) /p>

至于多线程:我觉得这里不够专业,无法提供解决方案,但我认为Herb Sutter's Effective Concurrency columns 是一个非常值得一读的主题。

编辑以解决 cmets 中的讨论

我没有写“C 风格的字符串处理不是更高效”。 (这句话肯定有很多否定,但因为我觉得被误读了,所以我尽量准确。)我说的是高级构造通常不会降低性能:std::vector 不是一般 比手动执行动态分配的 C 数组要慢,因为它是一个动态分配的 C 数组。当然,在某些情况下,根据特殊要求编写的代码会比任何通用解决方案执行得更好——但这并不一定意味着您必须求助于手动内存管理。这就是为什么我这样写的原因,如果这些东西是必要的,那么只能在类中很好地封装。

但更重要的是:在大多数代码中,差异并不重要。按钮在有人点击后按下 0.01 秒还是 0.05 秒都无关紧要,因此即使是 5 倍的速度增益在按钮的代码中也无关紧要。然而,代码是否崩溃,总是很重要。

总结一下我的论点:首先让它正常工作。这最好使用经过充分验证的现成构建块来完成。然后测量。然后使用久经考验的现成习语在重要的地方提高性能。

【讨论】:

我必须同意,我花费最多时间处理的问题是其他人过度非结构化软件中的并发问题。 对您声称 C 字符串样式性能不高的说法有点反对...我得到了 10 倍加速更改代码,该代码将字符串向量存储为存储 const char * begin,end 向量的代码对。 @Zan Lynx:是的,在某些情况下,您也可以通过使用字符串而不是 char* 来获得巨大的加速。比如,当你需要字符串的大小时。恒定时间 .size() 优于线性时间 strlen()。当然,您可以通过修复低效的代码来获得 10 倍的加速。不是 char* 具有神奇的效率,而是字符串向量是一种构建数据的愚蠢方式。 @nimrodm 为什么要调试标准库?当然它的主要优点是已经调试过了? +1:很好的答案。对其中一些 cmets 的一个普遍看法:通常,采用一段简单的、运行缓慢的代码并对其进行优化要比采用一段快速的损坏代码并使其工作更容易。【参考方案2】:

我实际上是要发布一个完全相反的问题 - 其他人是否像我一样发现您在使用 C++ 时几乎没有时间使用调试器?老实说,我不记得我最后一次使用它是什么时候了——那肯定是大约六个月前。

坦率地说,如果您将大部分时间都花在调试器上,我认为您的基本编码实践存在很大问题。

【讨论】:

我不能同意。那些只需要支持他们自己的(完美设计的)代码的程序员是有福的。大多数程序员还必须支持遗留(糟糕的、未记录的)代码。 我同意 - 可以证明好的代码是正确的(或足够接近),而大多数编写的 C 和 C++ 代码并没有那么严格。这意味着您最终不得不处理毫无意义的意外极端情况,然后就需要调试器了。 我在过去 15 年中处理的大部分遗留代码都是多线程中间件。在那种环境中,调试器比无用更糟糕——它改变了代码的时序,实际上隐藏了错误。 @Adrian。绝不。对于我从事的大多数项目来说,这是不可能的。这是明智的史蒂夫·马奎尔(Steve Maguire)颁布的这些“最佳实践”之一,但我从未相信过。 @Neil:当然没有必要使用调试器,但你必须有某种方法来计算程序流程和检查变量——即使你只是使用“std::cout”。你用什么来获取这些信息?当然,也许你能够在脑海中消化并执行整个程序——但这对于我们这些普通人来说真的不能是一般性的建议! :)【参考方案3】:

比赛条件。

这些是在调试(或问题跟踪器)中出现时仍然让我脊背发凉的少数几件事之一。本质上很难调试,而且非常容易创建。我的 C++ 软件中三个最常见的错误原因是竞争条件、对未初始化内存的依赖以及对静态构造函数顺序的依赖。

如果你不知道什么是比赛条件,那么它们很可能是你不稳定的原因;)

【讨论】:

【参考方案4】:

如果您确实处于已经有坏代码中断的位置,最好的计划可能是尽可能多地使用工具(操作系统/lib 级别的内存检查、自动化测试、日志记录、核心转储等)来找出问题区域。然后重写代码来做一些更具确定性的事情。大多数错误来自于大多数时间都在做的事情,但如果您使用正确的工具和方法,C++ 可以提​​供更强大的保证。

【讨论】:

【参考方案5】:

还没有看到提到过这个:

从没有虚拟析构函数的类继承。

【讨论】:

+1。只需 grep C++ 标准中的“未定义行为”并接收浮动错误的原因列表...【参考方案6】: 从未缓存的内存中读取,同时在内存上写回缓存行(这是一个正确的混蛋)。 缓冲区覆盖 堆栈溢出!

目前我能想到的唯一 3 个...以后可能会编辑 :)

【讨论】:

【参考方案7】: 缓冲区溢出 使用指向已删除对象的指针 返回无效引用或对超出范围对象的引用 未处理的异常 资源泄漏(不仅是内存)

无限递归

动态库版本不匹配

【讨论】:

我认为无限递归和未处理的异常都不算数。它们通常很容易被复制(因此,被跟踪)。【参考方案8】:

不是真正的 C++ 问题,而是在 C/C++ 项目中看到。

我必须处理的最棘手的问题是在我们的平台上启动操作系统时的初始化问题,这会导致异常崩溃。我们花了很多年才知道发生了什么。在此之前,我们通宵运行系统,如果它没有崩溃,那么通常没问题。 幸运的是,该操作系统已不再销售。

【讨论】:

【参考方案9】:

在分配之前或释放之后使用的地址和内存、分段错误、arrayoutofbounds、偏移量、线程锁、难以理解的运算符重载、内联汇编、void 退出和通常需要返回值的 void 使 math.h 函数值得一看的地方变得复杂因为与其他库相比,所有 math.h 函数都有工作参数和返回值过度无效、空性测试、nils、nulls 和 voids。我推荐的 4 个通用约定是返回值、参数、三元选择和可逆变化。避免使用空参数的向量(使用数组代替)很容易出错,并且在我的主观意见中,我避免使用 switch 语句以支持 if...elseif 或更抽象的“is”更易理解或可读性。

与脚本和解释相比,C++ 的前向兼容性也相当糟糕,尝试十年前的 Java,它仍然可以在以后的 vm 中保持不变且安全。

【讨论】:

实际上,数组容易出错,而向量则不然。毕竟,您自己将 arrayoutofbounds 指定为 bug 的一个重要原因;但是当你越界时,向量而不是数组会用 ASSERT 错误警告你。

以上是关于C ++中不稳定错误的最常见原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

AWS ECS ALB 错误(请求超时)

Sentinel之熔断降级

根据您的最低稳定性设置,该软件包在足够稳定的版本中不可用

熔断降级

php和c,c++的区别是啥?

Android中不稳定的BLE连接