Visual Studio 2015代码分析C6386警告缓冲区溢出

13

我已经阅读了很多关于Visual Studio Code分析警告C6386的内容,但是无法弄清楚我的代码中特定的问题。我将其简化为以下小程序:

unsigned int nNumItems = 0;

int main()
{
    int *nWords=nullptr;
    unsigned int nTotal;

    nTotal = 3 + 2 * nNumItems;
    nWords = new int[nTotal];

    nWords[0] = 1;
    nWords[1] = 2; // this is line 18, warning C6386

    delete[] nWords;
    return 0;
}

Analyze->Run Code Analysis->On Solution会出现以下警告:

file.cpp(18): warning C6386:在写入“nWords”时缓冲区溢出:可写大小为'nTotal*4'字节,但可能会写入'8'字节。

这个警告是真实的吗?如果我将全局变量移至本地,则此警告将消失!

int main()
{
    unsigned int nNumItems = 0;
...
}

但是在完整的代码中,这是一个成员变量,因此我无法这样做。

同样地,如果我将nTotal的定义移到“new int”中,我也可以消除警告:

    nWords = new int[3 + 2 * nNumItems];

但我无法这样做,因为在完整代码的其他地方引用了nWords

这只是Visual Studio静态代码分析器的问题,还是这段代码存在合法问题?


2
不会“修复”问题,但使用std::vector<int>代替。 - NathanOliver
1
当变量是全局的时候,编译器可能认为其他因素会改变它。所以如果 nNumItems = 0xffffffff,那么就会发生缓冲区溢出。但这并不改变警告在这种情况下是荒谬的事实。 - Mysticial
4个回答

7
静态代码分析很难,跟踪表达式的可能值,例如3 + 2 * nNumItems是困难的,而跟踪实际可能的值通常几乎不可能。这就是为什么它是一个警告,而不是错误。到目前为止,显而易见的问题就是这样。
现在,看看你如何描述这个警告的行为,我会打赌是一个"bug",或者更准确地说,应该用更少的压力、缺陷来说,在静态分析器中。
我可以看到一些想象中的可能原因,原始的nWords[1] = 2和全局的nNumItems。它们真的很奇怪,我不认为一个合理的分析员会向分析器添加这样的规则。此外,如果我是正确的,那么你也应该在nWords[0] = 1上看到这些警告。
你没有看到它们的事实证明了我的想法是错误的,所以我就到这里吧。
相反,我想集中讨论一下静态代码分析很难。无论分析器及其规则编写得多么好,对于某些情况,它都会出现错误,对于其他情况,它只是失败了,甚至不能猜测,对于其他情况,它会超时并放手。除非我们在人工智能或解决NP-hard问题方面有一些突破,否则你可能必须习惯这个事实,当你使用静态代码分析器时,你必须以它们可以理解的方式编写代码,而不是指望它们能够理解你所写的一切。
最后一个想法,当我看到这个错误时:

file.cpp(18): warning C6386: Buffer overrun while writing to 'nWords': the writable size is 'nTotal*4' bytes, but '8' bytes might be written.

第一件事就是注意到nTotal*48。如果你使用硬编码的值,你可能会得到一个类似于这样的错误:

file.cpp(18): warning C6386: Buffer overrun while writing to 'nWords': the writable size is '1024' bytes, but '8192' bytes might be written.

你看到nTotal*4似乎意味着静态代码分析器实际上未能猜测出nTotal下面的值,并将其留作符号名称,形成了一个与8无法比较的表达式。因此,分析器只能做它能做的事情——报告一个问题,并尽可能地描述它。不过,这只是我的猜测。
// 编辑 - Dan关于猜测的答案注意:nNumItems <- SIZE_MAX

我认为他可能会对 SIZE_MAX 感到困惑。我曾经尝试使用微软的 SAT solvers 玩了一下,它们在解决整数约束集方面效果很好。实际上,unsigned int x = SIZE_MAX; std :: cout << ((3+2*x)*sizeof(int)); 打印出 4(当然),这也是表达式小于 8 的唯一值。x 的这个值应该会在检查整数环域中 ((3+2*x)*4) < 8 的可满足性时被微软的约束求解器检测到,所以应该发出警告。但是,我希望警告包括结果并打印出类似以下内容:

nTotal*4 < 8 when {nTotal=1,nNumItems=4294967295}

因为分析器已经拥有这些信息。然而,这样详细的警告信息可能需要花费开发人员更多的时间来考虑格式和用户友好性,所以这可能是期望过高了。


4
静态分析至少和停机问题一样困难。这就是为什么静态分析工具不总是做得很好的原因。 - Mysticial

3

由于nNumItems是全局变量,因此代码分析器认为在您的代码执行之前可能会在其他地方将nNumItems设置为SIZE_MAX。您可以使用以下示例查看此内容:

size_t nNumItems = 0;

void foo()
{
    nNumItems = SIZE_MAX;
}
void bar()
{
    const size_t nTotal = 3 + 2 * nNumItems;
    auto nWords = new int[nTotal];

    nWords[0] = 1;
    nWords[1] = 2;
}

int main()
{
    foo();
    bar();

    return 0;
}

也许最好的解决方法是通过使用std::vector<int>来避免整个问题。

0
将全局变量设为常量:
const unsigned int nNumItems = 0;

1
可能其他部分的代码想要更改 nNumItems - Caleth

-1

这可能不是一个常见的解决方案,但在我的情况下,VS 生成了我认为是虚假的警告。重新启动 VS 后,警告消失了。


警告不会因重新启动IDE而消失。当然,您在启动时看不到它们,因为消息窗口将为空,但是每次编译代码时警告都会重新出现。此外,其他答案已经证明这不是虚假阳性。实际上有使用情况可能导致缓冲区溢出。 - wovano

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