C++ 如何正确重写断言代码以在msvc中传递/分析?

C++ 如何正确重写断言代码以在msvc中传递/分析?,c++,code-analysis,visual-c++,assert,C++,Code Analysis,Visual C++,Assert,Visual Studio为C/C++添加了代码分析(/analyze),以帮助识别错误代码。这是一个很好的特性,但当您处理旧项目时,您可能会被大量的警告所淹没 大多数问题都是由于旧代码在方法或函数的开头执行断言而产生的 我认为这是代码中使用的断言定义(来自afx.h) 示例代码: ASSERT(pBytes != NULL); *pBytes = 0; // <- warning C6011: Dereferencing NULL pointer 'pBytes' ASSERT(pBy

Visual Studio为C/C++添加了代码分析(
/analyze
),以帮助识别错误代码。这是一个很好的特性,但当您处理旧项目时,您可能会被大量的警告所淹没

大多数问题都是由于旧代码在方法或函数的开头执行断言而产生的

我认为这是代码中使用的断言定义(来自
afx.h

示例代码:

ASSERT(pBytes != NULL);
*pBytes = 0; // <- warning C6011: Dereferencing NULL pointer 'pBytes'
ASSERT(pBytes!=NULL);

*pBytes=0;// 您似乎认为
ASSERT(ptr)
某种程度上意味着
ptr
之后不为空。这不是真的,代码分析器也没有做出这样的假设。

PREFast告诉您代码中有缺陷;不要忽视它。你确实有一个,但你只是匆匆忙忙地承认了它。问题是:仅仅因为
pBytes
在开发和测试中从未为空,并不意味着它不会在生产中使用。你不能处理这种可能性。PREfast知道这一点,并试图警告您,生产环境是敌对的,并且会让您的代码留下大量毫无价值的字节

/咆哮

有两种方法可以解决这个问题:正确的方法和破解

正确的方法是在运行时处理空指针:

void DoIt(char* pBytes)
{
    assert(pBytes != NULL);
    if( !pBytes )
        return;
    *pBytes = 0;
}
这会让你安静下来

技巧是使用注释。例如:

void DoIt(char* pBytes)
{
    assert(pBytes != NULL);
    __analysis_assume( pBytes );
    *pBytes = 0;
}

编辑:描述预览注释。无论如何,这是一个起点。

/analyze
不能保证产生相关且正确的警告。 它可以而且将错过很多问题,并且还会出现一些误报(它将这些问题识别为警告,但它们是完全安全的,并且永远不会发生)

期望在/analyze中没有警告是不现实的

它指出了一种情况,即您取消引用它无法验证始终有效的指针。就PREfast所知,不能保证它永远不会为空

但这并不意味着它可以为空。只是证明其安全性所需的分析对于PREfast来说太复杂了

您可以使用特定于Microsoft的扩展来告诉编译器它不应该生成此警告,但更好的解决方案是保留此警告。每次使用/analyze编译时(不必每次编译时都使用/analyze),您都应该验证它确实出现的警告仍然是误报

如果您正确使用断言(在编程过程中捕捉逻辑错误,防止发生无法发生的情况),那么我认为您的代码或保留警告没有问题。添加代码来处理永远不会发生的问题是毫无意义的。您无缘无故地添加了更多代码和更复杂的问题(如果它永远不会发生,那么你就没有办法从中恢复,因为你完全不知道程序将处于什么状态。你所知道的只是它进入了一个你认为不可能的代码路径

但是,如果您使用断言作为实际的错误处理(在异常情况下,该值可以为NULL,您只是希望它不会发生),那么这是代码中的一个缺陷。然后需要正确的错误处理(通常是异常)

永远不要对可能出现的问题使用断言。使用它们来验证不可能的事情没有发生。当/analyze向您发出警告时,请查看它们。如果是误报,请忽略它(不要抑制它,因为今天是误报,明天签入的代码可能会将其变成真正的问题).

请记住,ASSERT()在零售版本中消失,因此C6011警告在上面的代码中绝对正确:您必须检查pBytes是否为非空以及执行ASSERT()。如果调试错误中满足该条件,ASSERT()只会将您的应用程序扔进调试器


我工作很多/分析和预告,所以如果你还有其他问题,请告诉我。

< P>我的合作作者David LeBlanc会告诉我,这个代码被破坏了,假设你正在使用C++,你应该使用一个引用而不是一个指针,而一个REF不能是空的:

首先,断言语句必须保证抛出或终止应用程序。经过一些实验后,我发现/analysis忽略了模板函数、内联函数或普通函数中的所有实现。您必须使用宏和do{}while(0)技巧,内联抑制

如果您查看Microsoft use uu analysis u success()在其宏中的定义,他们还提供了几段非常好的文档,介绍了迁移ATL以使用此宏的原因和方式

作为一个例子,我以同样的方式修改了CPPUNIT_ASSERT宏,以清除单元测试中成千上万的警告

#define CPPUNIT_ASSERT(condition)                                                                    \
do {    ( CPPUNIT_NS::Asserter::failIf( !(condition),                                                \
                                  CPPUNIT_NS::Message( "assertion failed" ),                         \
                                  CPPUNIT_SOURCELINE() ) ); __analysis_assume(!!(condition));        \
                                  __pragma( warning( push))                                          \
                                  __pragma( warning( disable: 4127 ))                                \
                                  } while(0)                                                         \
                                  __pragma( warning( pop))

您可以显示一些警告消息吗?您还可以显示您的
ASSERT
实现吗?
/analyze
不会在我的机器上抱怨
ASSERT
。我更新了描述,顺便说一句/analyze不会抱怨ASSERT,但它知道ASSERT可以跳过,并且作为一种副作用,它会触发警告拿铁r当使用变量时。这是正确的,但它没有回答问题。呃,你盲目地假设
pBytes
实际上可以是
NULL
。这不一定是一个有效的假设。PREfast只是安全起见:它经常给出误报。这就是为什么它在默认情况下没有启用。它会告诉你它发现了什么可疑,并且由您来验证它是否正确,如果正确,则解决问题。但是说您实际上需要处理空案例,因为PREfast这么说纯粹是胡说八道。如果它在逻辑上确实可以发生,请处理它。断言旨在捕获逻辑错误:逻辑上永远不会发生的事情。根据定义,这些是不可能记录的呃,你会的
#define CPPUNIT_ASSERT(condition)                                                                    \
do {    ( CPPUNIT_NS::Asserter::failIf( !(condition),                                                \
                                  CPPUNIT_NS::Message( "assertion failed" ),                         \
                                  CPPUNIT_SOURCELINE() ) ); __analysis_assume(!!(condition));        \
                                  __pragma( warning( push))                                          \
                                  __pragma( warning( disable: 4127 ))                                \
                                  } while(0)                                                         \
                                  __pragma( warning( pop))