C++中最难找到的错误

30

我很清楚,可能没有"最难找的C++错误",但我仍然对其他人能想到或已经遇到的问题感兴趣。

这个问题的想法是在与朋友讨论时产生的。我们一致认为,在提交源代码时故意包含错误可以很容易地破坏cpp项目...但是我们能想到的最好办法就是使用未初始化的变量(导致运行时出现随机分段错误)。我相信还有更好的方法...?!

错误代码的所需特征:

  • 乍一看必须像是有效的代码
  • 不能妨碍代码编译(太明显了)
  • 如果可能,错误应该看起来像是一个错误(如果有人发现它)
  • 错误必须严重到足以阻止软件发布(例如随机分段错误、代码逻辑故障等)

然而,虽然必须是显眼的,但在提交代码后不应立即变得明显...你会明白我的意思。

别担心,我们的考虑纯粹是理论性的(我们没有计划破坏任何项目)。我们只是认为这是一个足够好的思维实验,可以与他人分享 :-)

简而言之:

有什么最微妙的方式可以破坏源代码,在差异提交中(如git)可能不会被注意到,但最终会阻止软件发布?


14
哦,不要关闭这个,不要再证明SO是我们厌恶乐趣的地方! - Matteo Italia
2
链接包含两个不同的boost版本、运行时或其他已经看到它在操作中。 - liori
1
虽然我个人没有采取行动来关闭它,但这是有道理的,因为它未能满足常见问题解答标准中关于好问题的要求:实际问题的实用性和可回答性。闲聊、开放式的问题会降低我们网站的实用性,并将其他问题推到前页之外。 - Foggzie
1
真的,但这些问题通常涉及可衡量的信息和算法,其中极端情况(最高/最低/最快/最慢)可以在数学上得到证明或明确争论。询问一个人发现错误的能力是挖掘人类大脑,而不是计算机的大脑;StackOverflow成员通常只擅长后者。 - Foggzie
2
我认为这是一个很好的方法,可以熟悉代码中可能出现的最微小的错误。 - chris
显示剩余6条评论
8个回答

18

经典:

#define if while
#define else

嵌入在某个标题中。


回应 @WhozCraig 的评论,另外:

#define true (!!(__LINE__ % 10))

每十行代码中,true 将不会如此“真实”,但编译后的程序行为将始终保持一致...直到源代码发生变化,导致行为出现难以预料的变化。

在这个基础上:

#define for if(__LINE__ % 10) for

#define NULL (!(__LINE__ % 10))

或者考虑下面这个方案:

#define virtual 

这将会引起严重的问题 - 但仅在使用动态分派时才会产生,这可能使其检测变得更加棘手。


同样的方式:

#define dynamic_cast static_cast

// Fail early, fail often
#define throw std::abort();

1
@WhozCraig 把它改成模100000,这样几乎不可能再现。 - David Brown
1
然后就是 #define else else if (0)。显然我们不会像 Matteo 的例子那样这么做;那太傻了 =P - WhozCraig
@MooingDuck,override 赢了,我说得对吧? - chris
@chris:我在想抽象基类中出现了“函数声明但未定义”的链接错误,但是使用override也可以突出问题。 - Mooing Duck
作为一条注释,我认为Clang使得这些易于找到。 - Mooing Duck
显示剩余4条评论

16

不要太显而易见:

if (foo =! foobar)

我们可以添加一个技巧来消除编译器警告:

 if ( (i =! 3) && (j==1))

虽然我之前从未见过这种情况,但我可以想象追踪它有多么困难,特别是当它被埋在复杂的if-else-if嵌套中时。我不得不看两次代码才注意到它。 - WhozCraig
看起来这似乎是一个无意的失误 :) - perreal
1
@example,稍作修改以欺骗编译器。 - perreal
@perreal,我不理解"if(foo=!foobar)" 和 "if((i=!3)&&(j==1))",你能解释一下吗? - Computernerd
2
@Lim,这是一个赋值语句,foo = (!bar),即foo获取强制转换为布尔值的foobar的否定值。 - perreal
显示剩余3条评论

9
我曾经遇到一个问题,因为在发布版本中,我们使用的CArray(由微软作为MFC的一部分)随机出现段错误,但在调试版本中则没有问题。我们将其替换为std::vector后,问题得到解决。直到几个月后,有人告诉我CArray 不使用元素赋值运算符而是使用memcpy (source)。这显然会破坏任何具有非平凡赋值运算符的包含对象,但它是一个标准容器,所以每个人都认为它是安全的。因此,如果在一些关键位置将std::vector 替换为CArray,就可能会出现问题。

需要注意的是,Microsoft建议现在不要使用MFC容器,而应该使用STL容器。


1
哇,这是作者的纯恶意。 - chris
@chris 是由微软开发并作为 MFC 库的重要组成部分进行发布的。 - Mooing Duck
不确定那是否是您所指的,但是哇。无论如何,很可能没有50个人在开发那个类:p - chris
在寻找资源时,我偶然发现了这篇帖子,作者认为ATL容器类优于STL,但与此相关性不大。然后,Visual C++团队的代理产品单位经理Ronald Laeremans回复说,它们只是为了向后兼容而存在。 - Mooing Duck
现在这很有趣。这对于那些争论不要使用标准容器的人来说非常奇妙。 - chris

5

我认为,到目前为止,最令我沮丧的事情就是使用=而不是==。例如:

while(foo = bar) {}

替代

while(foo == bar) {}

基本上,任何使代码不正常工作而不是崩溃的事情都让我感到非常头疼。

其他需要注意的问题:

  • 使用错误的数学运算符 - + / *
  • 类似于 === 的误解,将 & 错误地理解为 &&| 错误地理解为 ||
  • 在像 vector<bool> 这样的东西中过早进行优化(如果你还不明白,请阅读这里
  • 有两个或更多名称相同的类并使用了错误的类。

3
但是任何现代编译器都会在条件语句中的赋值操作上发出警告。 - Kerrek SB
1
@KerrekSB同意。聪明的人甚至会压制警告,如果你花时间将你的赋值埋在另一组括号中,无论你是否使用结果来评估第三个操作数。所以if (x=func())会发出警告,但是if ((x = func()))或其预期用途if ((x=func()) == val)不会发出这样的警告。 - WhozCraig

4

也许这是个无耻的抄袭这篇问题,但我认为它非常适合这个类别。

如果你有任何硬编码的字节串,而且它们长度相同(例如,你可能用于网络通信的内容),你可以利用这个机会来伪装它:

const unsigned char someBytes[] = "text\0abc123";

通过一个小开关,就可以变成:

const unsigned char someBytes[] = "text\0123abc";

区别在于第一个有12个字符,而第二个只有10个字符,这是因为中间的八进制字面量。如果遇到这种情况,这无疑会让跟踪变得更加困难。


为什么八进制字面量在中间对字符数组有影响? - Computernerd
@Lim,字符串中的八进制字面量采用\0###的形式,其中#是从0到7的数字。因此,第二个数字将0、1和2作为八进制字面量的一部分吞噬掉,字符串变得更短。如果您有某些总是期望相同长度的字符串,它将读取超出您提供的末尾,导致未定义的行为。 - chris
@Lim,没问题。在那个其他的问题出现之前,我甚至从未在字符串中看到过它。 - chris

3

虽然你还没有成为它的受害者,但隐式转换可能会导致一些不好的结果。看看这个例子:

class Foo {
public:
    Foo(int a, OtherClass* b = NULL);
};

现在(没有明确的关键字),每个期望通过值/常量引用传递Foo的方法也将接受int!


进行这样的更改可能会破坏现有的代码,或者不会被触发。 - Mooing Duck

2
struct
{ int foo
char bar
}


while (foobar != 10);
{
     //do something here
 } 

1)在结构体后面忘记加分号或在while循环后面加上分号。

randn() //user created function
rand()  //library function

2) 给一个函数命名与预定义函数相似的名称


1
第一个不是编译器错误吗? - Mooing Duck
@MooingDuck 是的,这是一个编译器错误,但错误信息有误导性:不能在返回类型中定义新类型。 - Computernerd
2
我看过那个错误一千次,知道确切的原因,我认为大多数经验丰富的C ++程序员也会如此。 - Mooing Duck

2

这并不是一个错误,只是当我发现发布版本比调试版本慢时,我在某个随机源文件中这样做,这让我感到很烦。 :)

#ifdef NDEBUG
namespace {
    struct foo {
        foo() { sleep(rand() % 4); }
    } bar;
}
#endif

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接