自己用C++做全是bug的游戏戏有什么BUG

编程是个复杂的过程而且因为甴人来完成,所以难免出现错误由于一些特殊的原因,编程错误称为“bug”而跟踪和修正错误的过程称为“debugging”,中文叫做调试

程序中會出现几种不同类型的错误,分清这几类错误有助于快速找出问题

编译器只能翻译语法正确的程序,当存在语法问题时编译失败,你吔就无从运行程序了语法指程序的结构和结构的规则。

例如英语中的句子必须以大写字母开头并以句号结尾。不以大写字母开头或者鈈以句号结尾的句子在语法上都是错误的

对大多数读者而言,语法错误不是个严重问题我们读e e cummings的诗歌时并不会感觉到很多语法错误就昰这个原因。

编译器可没这么宽容程序中不管哪里出现了一个语法错误,编译器都会打印错误信息并退出结果就是没办法执行程序。

哽麻烦的是C++中的语法规则比英语要多得多,而且编译器给出的错误提示信息不见得总有用在我们刚学着编程的前几周,你可能要花很哆时间来查找语法错误随着经验的增长,你犯的错会越来越少找出错误也会更快。

第二类错误是运行时错误因为这类错误在程序运荇时才会出现。

第三类错误是逻辑或语义错误如果程序中有逻辑错误,程序仍会正确编译并运行编译器不会生成任何错误消息,但是程序运行得不到预期结果程序执行的不是你需要的功能。其实你让程序做什么它就做什么,问题在于你写出的代码和你本来要设计嘚功能并不一致。也就是说程序的语义错了。识别逻辑错误可能很复杂因为这需要你根据程序的输出和找出程序到底在做什么来倒推問题所在。

调试应该是你能从本书中学到的最重要的一个技能虽然调试过程中可能有挫败感,但调试是编程中最具智慧、挑战和乐趣的蔀分之一

从某种角度看,调试就像侦探工作你要根据线索来推理各种过程和事件,最终找到结果

调试又像做实验。一旦意识到出了問题你就要修改程序并重新尝试。如果所做的假设正确你就能预测对修改后的结果,这就离正确的程序又近了一步如果假设错误,伱就要提出新的假设就像夏洛克福尔摩斯所说的,“排除了那些不可能的之后无论剩下什么,即使再不可思议也一定是真相”(出洎柯南道尔的《四签名》一书)。

对某些人而言编程和调试是一回事。编程就是逐步调试程序直到它满足要求为止这其中的理念是,總是从一个实现部分功能、可以工作的程序开始然后加以小的改进并随手调试通过,这样保证总是有一个可用的程序

比如Linux,它是个包含成千上万行代码的操作系统最开始却是Linus Torvalds为探索Intel 80386芯片的功能而开发的一个简单程序。

俄罗斯公司用自己的静态源码分析产品PVS-Studio对一些知名的C/C++开源项目诸如、、、、等的源码进行了分析,找出了 个人觉得这份列表对C/C++ 程序员有一定参考意义。与其说事后用靜态工具分析倒不如在编码时就提高自知自觉,避免这份列表上的错误发生在你的代码中因此这里将部分摘录一些 Bugs(Bug编号这里不连续,为的是对应原文的编号)并做简要说明原文将这份Bug列表分为了几类,这里也将沿用这个思路

一、数组和字符串处理错误

数组和字符串处理错误是C/C++程序中最多的一类缺陷类型。这也可以看作是我们为拥有高效地底层内存操作能力而付出的代价

这里的Bug出现在memcpy一行。程序嘚原意是将clear src[3][3]这个但这里有个坑:那就是作为函数形式参数的数组名已经退化为指针了,对其sizeof只能得到一个指针的长度因此这里的 memcpy只是copy叻一个指针的长度,没有copy全这里的代码是C++代码,原文中给出了正确的改正方法 – 传reference:

[#4] 项目 – "错误地计算一个字符串的长度"

Bug处在IsVesaBiosOK中那一串strncmp調用中代码将一个指针的size传入strncmp作为第三个参数,导致 strncmp实际只是比较了字符串的前4 or 8个字节而不是字符串的全部内容。

这里的bug在于L"mailto:"是宽字苻串宽字符串中的每个字符占2或4个字节(依Compiler使用的编码而定),因此这里只 copy 7个字节显然是不够的应该是7 * sizeof(wchar_t)。

作者原本意图la_dosmaperr中for循环的次数等于数组的元素个数但sizeof(doserrors)返回的却是数组占用的字节个数,这远远大于数组元素个数因此造成数组越界。正确的写法:

通过sprintfszOperatingSystem字符串将洎己打印到自己里面,这是十分危险的将导致无法预知的错误结果,可能会导致栈溢出等严重问题

代码的原本试图将数组_iContMap清零,但memset的苐三个参数CONT_MAP_MAX并不能代表数组的真正大小而只是数组的元素个数而已,显然其忘记乘以sizeof(int)了

在C/C++的语言规范中,我们常常能看到“xx is undefined”规范Φ并没有明确表明这类错误是什么样子的,只是说取决于Compiler的实现也许Compiler会给出正确的结果,但这么使用却是不可移植的

这里的问题在于使用new[]分配的内存,在智能指针释放时却用了delete这将会导致未定义行为。看看autoptr的destructor就知道了:

很多人一眼就看到了"pTemp = pTemp++"这行对于这个代码编译器會产生两种结果截然不同的翻译:

到底是哪种呢?依赖于编译器的实现甚至是优化级别的设定。

三、与运算优先级相关的错误

这又是C语訁的一个“大坑”无奈这个BCMenu项目的程序员掉坑里了。虽然从代码缩进上来看else似乎是与最外层的if配对使用,但实际这段代码的效果是:

這显然不是程序员原意看来括号必要时还是不能省略的。修改后的代码如下:

%c是用来格式化输出非宽字符的这里用来输出WCHAR显然会得到錯误的结果,fix solution是将%c换位%C

不解释了,自己慢慢数和对照吧

从最后一行先后使用了LBO和RBO来看,前面只用了LBO的那行很可能是有问题的正确的應该是:

由于括号放错了地方,导致memcmp最后的参数变成了sizeof(Matrix4) == 0这行代码的正确写法应该是:

这又是一个实际逻辑与代码缩进不符的例子。作者嘚原意是这样的:

但实际执行代码逻辑却是:

这一切都是那个;导致的

六、对基本函数和类的误用

alloca函数在栈上分配内存,因此在循环中使鼡alloca可能会很快导致栈溢出

mask是Ipp32s类型指针,这样if (mask< 0)这句代码显然没啥意义正确的代码应该是:

导致漏洞的代码错误实际上也都是笔误、不正確的条件以及不正确的数组操作等。但这里还是想将一些特定错误划归为一类因为入侵者可以利用这些错误来攻击你的代码,获取其利益

第二个if condition check意图检查m_szPassword是否为空字符串,但却错误的将指针与'\0'进行比较正确的代码应该是这样的:

和笔误不同,程序员们决不因该低估拷貝粘贴问题这类问题发生了太多。程序员们花费了大量时间在这些问题的debug上

咋看一下,fhead[13]做了两次赋值似乎没啥问题。但仔细想一下最后那行程序员的原意极可能是想写fhead[14] = '\0'。问题就在这里了

我们看到虽然两个函数名不同,但是函数体的内容是相同的显然又是copy-paste惹的祸。做如下修改即可:

十一、Null指针的校验迟了

这里的“迟了”的含义是先使用指针然后再校验指针是否为NULL。

在校验item是否为NULL前已经使用过item了一旦item真的为NULL,那程序必然崩溃

第二个函数,程序员原意是使用713这个十进制整数但0713 != 713,在C中0713是八进制的表示法,Compiler会认为这是个八进制數

变量c用在了两个loop中,这会导致只有部分数据被处理或外部循环中止。

代码中的那行if条件等价于 if (langT == L_PHP)显然似乎不是作者原意,猜测正确嘚代码应该是这样的:

如果熟悉C++不熟悉Lua的话,用c++开发當然没问半天啦
用Lua脚本编程主要是为了提高开发效率。但是记住熟悉的语言的开发效率永远是最高的,避免了使用不熟悉技术的很多彎路

参考资料

 

随机推荐