STL 番外:记一个 bug 的解决过程
记录一个比较神奇的 bug
在测试 deque 的过程中,发现:
当在 d1 的倒数第二个位置插入两个 7 的操作后,d1 的最后一个数字莫名变成了 9,在一番对照后发现这个数字是前面d1.assign(8, 9)
遗留的产物,为什么会出现这种现象呢?
首先,之前的 insert
操作没有问题,并且 deque
也能正常工作,且除了这里的异常外别的测试均能顺利通过,说明极大概率是这个 insert
的重载出了问题,我们回到原函数中查看:
可以看到插入 n 个元素的 insert 操作除前后插入的特殊情况外,剩余的操作全权交由 fill_insert
负责,因此我们再跳转到 fill_insert
fill_insert
是一个很长的函数,根据要插入的位置的前后元素个数的比较来执行更有效率的插入方式,且每种方式都要根据要插入元素个数与 deque 中对应方向上元素个数的关系来执行不同的操作。这里我们在倒数第二个位置插入,且插入元素个数(2)大于 pos
后方元素个数(1),因此问题一定出在 973 ~ 978 之间的语句中。
根据代码可以看出,函数的执行逻辑为:1. 填充超出原数组长度部分的新元素 2. 将原始数组中的该移动的部分移动至正确位置 3. 填充原始数组内需要填充的新元素。
因此,既然是原始数组的数据移动出了问题(测试中的 6 未能正确地移动到最后一个位置),必然是第二句代码除了错误,排查之下发现,将本该是 pos + n
的敌方写成了 finish_ + n
,这样就导致元素被移动到了超过数组边界的位置,因此出错,改正之后得到了正确的结果。
但这依旧无法解释为什么会出现神奇的 9 ,这个数字,要知道在 assign
一个较短的数组后,原始数组多余的部分应该全被摧毁掉才对,怎么会还残留于数组中呢?我们继续思考。
首先,既然是 assign
函数出了问题,那么就查看该函数的实现:
可以看出,不同版本重载的 assign
具体操作交由 fill_assign
及 copy_assign
完成,查看这两个函数:
可以看出,无论是copy_assign
还是 fill_assign
,都需要使用 erase
来消除多余的元素,再查看 erase
:
会发现除了擦除整个数组的操作,其余操作都是先移动元素再直接调用 allocator::destory
来销毁中间的元素,似乎发现了一些端倪。
在又经历了几轮函数的中转,我们终于来到了答案的埋藏之地 —— construct.h
,这是一个负责对象的构造与析构的头文件,在 allocator
中直接调用了这里的 destory
来实现相应功能。
查看之下,我们所需要的真相终于浮出水面!在 destory
中,根据对应类型是否具有平凡析构来调用不同的 destory_cat
,其中,拥有平凡析构函数的类型,在 destory
中不做任何处理!而测试中的所使用的整数类型,自然拥有平凡析构。因此,destory
没有做任何操作,这才导致了之前的 bug 的发生。
由此可以看出,维护一个正确的 deque
迭代器有多么重要,因为迭代器之外很有可能是已被弃用却依然存在的数据,仍然可以轻松访问到!