我参加了比赛,有一次我们有调试问题。我必须在C和C ++中设计一些非常好的调试问题。
如何在调试时创建一些好问题?在设计问题时我应该考虑哪些方面?
答案 0 :(得分:8)
我的头脑风暴会议:
微妙的内存泄漏总是很好。与类,构造函数,复制构造函数和析构函数混在一起,你应该能够轻松地创建一个难以发现的问题。
阵列循环的一次性错误也是经典的。
然后你可以通过玩一些东西来简单地弄乱读者的思想。创建具有微妙不同名称的变量,具有随机(和略微不同)名称的变量等,然后让他们尝试找到您混淆length
和lenght
的地方。不要忘记套管差异。
调用约定也可能被滥用来创建微妙的错误(比如颠倒参数的顺序)。
另外,让我们不要忘记从棘手的预处理器定义和模板中获得无穷无尽的乐趣(你知道C ++模板应该是Turing-complete吗?)元编程错误应该是有趣的。
想到的下一个想法是提供正确的程序,但输入数据有缺陷(当然是巧妙的)。然后程序将因缺少错误检查而失败,但是在人们意识到他们在错误的地方寻找问题之前还需要一段时间。
竞赛条件通常难以重现和修复,尝试使用多线程。
偶然检查可能很容易错过下溢/溢出。
最后,但并非最不重要 - 如果你是一名程序员,请尝试记住你花了两周时间解决的最后一个大问题。如果您不是计算机程序员,请尝试查找并询问他们。我是一名.NET程序员,所以不幸的是,我的经验与你对C / C ++的要求关系不大。
答案 1 :(得分:5)
对于一些简单的“查找此源代码中的错误”练习,请查看PC-lint's bug of the month archive。
答案 2 :(得分:3)
除了上述内容外,还要考虑副作用。例如:
// this function adds two ints and returns the sum
int add_em(int &one, int &two)
{
two += one;
return two;
}
正如您所看到的,此代码修改了 two 变量,尽管注释未提及...
答案 3 :(得分:1)
调试范围很广,在您的问题中反映这一点可能是明智之举。没有详细说明,我可以看到以下类别:
此类别中的问题只有源代码,没有任何关于错误的进一步提示。 实际的错误在这里可能会有很大不同:从缓冲区溢出等简单的逻辑错误和计数错误到错误的假设,通过舍入错误之类的数学错误,再到错误的假设,如假设特定的字节顺序或填充。
此类别中的问题包含源代码,以及期望与实际输出/行为。 例如。 “这个程序应该打印42,但打印出内存不足。为什么?”
此类别中的问题不仅包括源代码,还包括崩溃转储。
答案 4 :(得分:1)
我将在上面的答案中添加另一种形式的错误,即错误使用某些库或API代码。从表面上看,一切看起来都不错,但有一些人不知道的警告(例如,先决条件或限制)。在这些情况下,交互式调试器本身并不那么有效,因为它们不会向您公开这些信息(它通常隐藏在文档中)。
例如,我过去曾做过这方面的研究。我给了人们使用的代码(Java中的消息传递API),其中的错误是程序在您尝试接收消息时就会卡住。以交互方式调试这几乎是不可能的。他们必须手动弄清楚发生了什么,并意识到其中一个队列没有正确设置。
这些错误实际上很常见。
答案 5 :(得分:0)
真实世界的调试包括查找同步问题以及托管/非托管边界之间的问题,因此请考虑使用c / c ++ / c#作为选项。
或者为了真正有趣,请考虑仅使用c#并查找内存泄漏。
此外,您还需要提及允许使用哪些工具。在Windows上,有几十种可用的调试工具。