我多次注意到程序员在简单的数据复制功能中犯了错误。为了撰写详尽的文章,将来该主题将需要更多时间来研究和选择材料。但是我想分享一些最近注意到的例子。Baadera-Meinhof现象?不,我不这么认为
作为PVS-Studio团队的一员,我在各种项目中遇到了很多错误。像DevRel一样-我喜欢谈论它:)。今天,我决定谈论不正确实现的数据复制功能。我不只一次遇到过这种不成功的功能。但是我没有写出来,因为我对此并不重视。但是,由于我注意到了这一趋势,是时候开始收集它们了。首先,我将分享注意到的最后两个案例。有人可能会争论两种情况-这不是规律性。而且,也许我之所以引起他们注意是因为他们经过了很短的时间后遇到了我,并引发了Baader-Meinhof现象。Baader-Meinhof现象,也是频率的错觉,是一种认知扭曲,在这种扭曲中,最近发现的信息在短时间后再次出现,通常被认为是异常重复。我认为并非如此。我已经对比较功能进行了这样的观察,然后被收集的资料证实了这一点:“ 邪恶生活在比较功能中”。好吧,让我们说清楚。到目前为止,仅给出两个示例的介绍太长了:)。范例N1
在有关Zephyr RTOS验证的文章中,我描述了实现strdup函数类似物的尝试失败:static char *mntpt_prepare(char *mntpt)
{
char *cpy_mntpt;
cpy_mntpt = k_malloc(strlen(mntpt) + 1);
if (cpy_mntpt) {
((u8_t *)mntpt)[strlen(mntpt)] = '\0';
memcpy(cpy_mntpt, mntpt, strlen(mntpt));
}
return cpy_mntpt;
}
PVS-Studio警告:V575 [CWE-628]'memcpy'函数不会复制整个字符串。使用'strcpy / strcpy_s'函数保留终端null。shell.c 427分析器报告memcpy函数复制了该行,但没有复制终端0,这非常可疑。似乎该终端0复制到这里:((u8_t *)mntpt)[strlen(mntpt)] = '\0';
不,这里有一个错字,因为终端零被复制到其自身。请注意,写入mntpt数组,而不是cpy_mntpt。如此一来,mntpt_prepare函数将返回一个不包含零的字符串。实际上,程序员想这样写:((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';
目前尚不清楚为什么编写的代码如此混乱和不规范。结果,在小的,简单的功能中犯了一个严重的错误。该代码可以简化为以下选项:static char *mntpt_prepare(char *mntpt)
{
char *cpy_mntpt;
cpy_mntpt = k_malloc(strlen(mntpt) + 1);
if (cpy_mntpt) {
strcpy(cpy_mntpt, mntpt);
}
return cpy_mntpt;
}
示例N2
void myMemCpy(void *dest, void *src, size_t n)
{
char *csrc = (char *)src;
char *cdest = (char *)dest;
for (int i=0; i<n; i++)
cdest[i] = csrc[i];
}
我们自己没有使用PVS-Studio识别此代码,但是我在StackOverflow网站上偶然遇到了它:C和静态代码分析:这比memcpy安全吗?但是,如果您使用PVS-Studio分析仪检查此功能,他将正确地注意到:- V104在算术表达式中将 “ i”隐式转换为memsize类型:i <n test.cpp 26
- V108索引类型错误:cdest [不是内存大小类型]。请改用memsize类型。test.cpp 27
- V108错误的索引类型:csrc [不是memsize类型]。请改用memsize类型。test.cpp 27
确实,此代码包含一个缺陷,如StackOverflow的答案所示。您不能将int变量用作索引。几乎可以肯定,在64位程序中(我们不考虑奇异的体系结构),int变量将为32位,并且该函数最多可以复制INT_MAX个字节。那些。不超过2 GB。使用较大的缓冲区进行复制时,将发生符号变量的溢出,从C和C ++的角度来看,这是未定义的行为。顺便说一句,不要试图确切猜测错误将如何表现出来。这实际上是一个困难的主题,您可以在文章“ 未定义的行为比您想像的要近 ”中阅读。有趣的是,此代码的出现是为了消除Checkmarx分析器中调用memcpy函数时发生的某种警告。程序员没有想出什么比自己骑自行车更好的了。尽管复制功能简单,但事实仍然是错误的。就是说,实际上,这个人最有可能比过去做得更糟。他没有理解警告的原因,而是通过编写自己的函数(使分析器混乱)掩盖了问题。加号使用int作为计数器增加了一个错误。哦,是的,这样的代码仍然会阻碍优化。使用您自己的代码而不是高效的优化memcpy函数效率低下。不要这样做 :)结论
好吧,我才刚刚起步,可能要花一年多的时间才能积累有关该主题的详尽出版物。实际上,直到现在,我才开始写出这种情况。感谢您的关注,并期待PVS-Studio分析仪在您的C / C ++ / C#/ Java代码中发现什么有趣的东西。
如果您想与讲英语的读者分享这篇文章,请使用翻译链接:Andrey Karpov。开始我的复制功能中的错误收集。