在Release版本中使用assert()时避免使用未使用的变量警告

时间:2009-04-22 13:43:03

标签: c++ warnings assertions

有时使用局部变量的唯一目的是在assert()中检查它,就像这样 -

int Result = Func();
assert( Result == 1 );

在Release版本中编译代码时,通常会禁用assert(),因此这段代码可能会产生一条关于Result被设置但从未读过的警告。

可能的解决方法是 -

int Result = Func();
if ( Result == 1 )
{
    assert( 0 );
}

但它需要太多的打字,不容易在眼睛上并导致总是检查条件(是的,编译器可能会优化检查,但仍然)。

我正在寻找另一种方法来表达这个assert(),其方式不会导致警告,但仍然易于使用并避免更改assert()的语义。

(在此区域代码中使用#pragma禁用警告不是一种选择,并且降低警告级别以使其消失也不是一种选择......)。

16 个答案:

答案 0 :(得分:49)

我们使用宏来明确指出何时未使用的东西:

#define _unused(x) ((void)(x))

然后在你的例子中,你有:

int Result = Func();
assert( Result == 1 );
_unused( Result ); // make production build happy

这样的方式(a)生产构建成功,(b)在代码中显而易见的是变量未被设计未使用,而不是它被遗忘。当不使用函数的参数时,这尤其有用。

答案 1 :(得分:23)

我无法给出比这更好的答案,解决这个问题,还有更多:

Stupid C++ Tricks: Adventures in assert

#ifdef NDEBUG
#define ASSERT(x) do { (void)sizeof(x);} while (0)
#else
#include <assert.h>
#define ASSERT(x) assert(x)
#endif

答案 2 :(得分:9)

您可以创建另一个允许您避免使用临时变量的宏:

#ifndef NDEBUG
#define Verify(x) assert(x)
#else
#define Verify(x) ((void)(x))
#endif

// asserts that Func()==1 in debug mode, or calls Func() and ignores return
// value in release mode (any braindead compiler can optimize away the comparison
// whose result isn't used, and the cast to void suppresses the warning)
Verify(Func() == 1);

答案 3 :(得分:8)

int Result = Func();
assert( Result == 1 );

这种情况意味着在发布模式下,您真的需要:

Func();

Func无效,即返回结果,即查询

据推测,除了返回结果之外,Func会修改某些内容(否则,为什么还要调用它而不使用其结果?),即它是命令

通过命令查询分离原则(1),Func不应该同时是命令和查询。换句话说,查询不应该有副作用,命令的“结果”应该由对象状态的可用查询表示。

Cloth c;
c.Wash(); // Wash is void
assert(c.IsClean());

优于

Cloth c;
bool is_clean = c.Wash(); // Wash returns a bool
assert(is_clean);

前者没有给你任何警告,后者确实如此。

所以,简而言之,我的回答是:不要写这样的代码:)

更新(1):您要求提供有关命令查询分离原则的参考资料。 Wikipedia信息量很大。我在Bertrand Meyer的Object Oriented Software Construction, 2nd Editon中读到了这种设计技巧。

更新(2): j_random_hacker注释“OTOH,每个”命令“函数f()以前返回的值现在必须设置一些变量last_call_to_f_succeeded或类似”。这仅适用于在合同中不承诺任何内容的函数,即可能“成功”或未成功的函数,或类似的概念。使用按合同设计,相关数量的函数将具有后置条件,因此在“Empty()”之后,对象将为“IsEmpty()”,并在“Encode”之后( )“消息字符串将是”IsEncoded()“,无需检查。以同样的方式,并且有些对称,在每次调用过程“X()”之前,不要调用特殊函数“IsXFeasible()”;因为你通常会在设计中知道你在通话时已经达到了X的先决条件。

答案 4 :(得分:3)

您可以使用:

Check( Func() == 1 );

并根据需要实施Check(bool)功能。它可以使用assert,也可以抛出特定的异常,在日志文件或控制台中写入,在调试和发布中有不同的实现,或者所有的组合。

答案 5 :(得分:3)

对于最近的C ++,我只会说:

[[maybe_unused]] int Result = Func();
assert( Result == 1 );

有关此属性的更多详细信息,请参见https://en.cppreference.com/w/cpp/language/attributes/maybe_unused

(void)Result技巧相比,我更喜欢它,因为您可以直接修饰变量声明,而不是事后才添加。

答案 6 :(得分:2)

这是对断言的不好用,恕我直言。断言并不是一种错误报告工具,它意味着断言前提条件。如果在其他地方没有使用Result,那么这不是一个先决条件。

答案 7 :(得分:2)

最简单的方法是,如果断言存在,则仅声明/分配这些变量。如果断言赢得,则NDEBUG宏被明确定义(这样做只是因为-DNDEBUG是一种禁用调试的便捷方式,我认为),所以这个经过调整的@ Jardel答案的副本应该有效(参见@AdamPeterson对该答案的评论):

#ifndef NDEBUG
int Result =
#endif
Func();
assert(Result == 1);

或者,如果这不符合您的口味,可以使用各种变体,例如:这样:

#ifndef NDEBUG
int Result = Func();
assert(Result == 1);
#else
Func();
#endif

一般情况下,请注意,不同的翻译单元永远不可能使用不同的NDEBUG宏状态构建 - 特别是re。断言或公共头文件中的其他条件内容。危险在于您或您的库的用户可能会意外地从库的编译部分内部使用的内联函数实例化内联函数的不同定义,悄然违反one definition rule并使运行时行为未定义。

答案 8 :(得分:2)

使用C ++ 17,我们可以做到:

[[maybe_unused]] int Result = Func();

尽管与断言替换相比,它涉及一些额外的输入。参见this answer

注意:添加此内容是因为它是“ c ++断言未使用的变量”的第一个Google匹配。

答案 9 :(得分:1)

您应该在返回值之前移动函数内部的断言。您知道返回值不是未引用的局部变量。

另外,在函数内部更有意义,因为它创建了一个具有OWN前后条件的自包含单元。

如果函数返回一个值,你可能会在这个返回值上以释放模式进行某种错误检查。所以它不应该是一个未引用的变量。

编辑,但在这种情况下,帖子条件应为X(参见评论):

我强烈不同意这一点,应该能够从输入参数确定post条件,如果它是成员函数,则任何对象状态。如果全局变量修改函数的输出,则应重新调整函数的结构。

答案 10 :(得分:1)

当然,您使用宏来控制断言定义,例如“_ASSERT”。所以,你可以这样做:

#ifdef _ASSERT 
int Result =
#endif /*_ASSERT */
Func();
assert(Result == 1);

答案 11 :(得分:1)

大多数答案建议在static_cast<void>(expression)版本中使用Release技巧来抑制警告,但如果您打算真正进行检查Debug,这实际上是次优的。有问题的断言宏的目标是:

  1. Debug模式
  2. 执行检查
  3. Release模式
  4. 中不执行任何操作
  5. 在所有情况下均不发出警告
  6. 问题在于void-cast方法无法达到第二个目标。虽然没有警告,但您传递给断言宏的表达式仍将被评估。例如,如果您只是进行变量检查,那可能不是什么大问题。但是,如果你在断言检查中调用某些函数,如ASSERT(fetchSomeData() == data);(这在我的经验中很常见),该怎么办?仍会调用fetchSomeData()函数。它可能快速而简单,也可能不是。

    您真正需要的不仅是警告抑制,而且更重要的是 - 仅限调试检查表达式的非评估。这可以通过我从专门的Assert库中获取的简单技巧来实现:

    void myAssertion(bool checkSuccessful)
    {
       if (!checkSuccessful)
        {
          // debug break, log or what not
        }
    }
    
    #define DONT_EVALUATE(expression)                                    \
       {                                                                 \
          true ? static_cast<void>(0) : static_cast<void>((expression)); \
       }
    
    #ifdef DEBUG
    #  define ASSERT(expression) myAssertion((expression))
    #else
    #  define ASSERT(expression) DONT_EVALUATE((expression))
    #endif // DEBUG
    
    int main()
    {
      int a = 0;
      ASSERT(a == 1);
      ASSERT(performAHeavyVerification());
    
      return 0;
    }
    

    所有魔法都在DONT_EVALUATE宏中。很明显,至少逻辑上对你的表达式的评价从来不需要它。为了加强这一点,C ++标准保证只评估条件运算符的一个分支。这是引用:

      

    5.16条件运算符[expr.cond]

         

    logical-or-expression?表达式:赋值表达式

         

    条件表达式从右到左分组。第一个表达是   在上下文中转换为bool。它被评估,如果是真的,那么   条件表达式的结果是第二个的值   表达式,否则表达式的表达式。只有其中一个   表达式被评估。

    我已经在GCC 4.9.0,clang 3.8.0,VS2013 Update 4,VS2015 Update 4中测试了这种方法,其中警告级别最苛刻。在所有情况下都没有警告,并且永远不会在Release构建中评估检查表达式(实际上整个事情已经完全优化了)。但是请记住,如果你在断言宏中放置具有副作用的表达式,使用这种方法会很快遇到麻烦,尽管这首先是一种非常糟糕的做法。

    另外,我希望静态分析仪可以警告表达式的结果始终是恒定的&#34; (或类似的东西)采用这种方法。我已经使用clang,VS2013,VS2015静态分析工具对此进行了测试,并没有收到此类警告。

答案 12 :(得分:0)

int Result = Func();
assert( Result == 1 );
Result;

这将使编译器停止抱怨没有使用Result。

但是你应该考虑使用一个在运行时做一些有用的东西的断言版本,比如将日志描述性错误记录到可以从生产环境中检索的文件。

答案 13 :(得分:0)

我会使用以下内容:

#ifdef _DEBUG
#define ASSERT(FUNC, CHECK) assert(FUNC == CHECK)
#else
#define ASSERT(FUNC, CHECK)
#endif

...

ASSERT(Func(), 1);

这样,对于发布版本,编译器甚至不需要为assert生成任何代码。

答案 14 :(得分:0)

如果此代码在函数内部,则执行并返回结果:

bool bigPicture() {

   //Check the results
   bool success = 1 != Func();
   assert(success == NO, "Bad times");

   //Success is given, so...
   actOnIt();

   //and
   return success;
}

答案 15 :(得分:0)

// Value is always computed.  We also call assert(value) if assertions are
// enabled.  Value is discarded either way.  You do not get a warning either
// way.  This is useful when (a) a function has a side effect (b) the function
// returns true on success, and (c) failure seems unlikely, but we still want
// to check sometimes.
template < class T >
void assertTrue(T const &value)
{
  assert(value);
}

template < class T >
void assertFalse(T const &value)
{ 
  assert(!value);
}