3

我想知道在启用任何类型的 gcc/g++ 优化的情况下,以下代码是否会在编译时引发错误或警告。

int a;
a = func();
if (a == 2) {
    assert(false);
}

我认为以下代码可以在发布配置中引发警告“设置但未使用的变量”。

int a;
a = func();
assert(a != 2);

但是上面的代码呢?(gcc可以删除 if 语句本身,因为在 if 语句或 if 块中(在发布版本中)不会做任何事情,然后抛出警告“未使用但设置变量”)

编辑:这绝对不是关于减少代码或 exe 大小的问题。我想知道在任何构建配置中都能成功的一段代码。

编辑:我们在发布模式下禁用断言

4

6 回答 6

4

根据我的测试,以下代码会生成警告-Wall -Wextra -O2 -DNDEBUG

int a = func(); // warning: unused variable ‘a’
assert(a != 2);

但是下面的代码没有:

// no warnings
int a;
a = func();
assert(a != 2);

但是,您始终可以通过强制转换为来抑制未使用的变量警告void

int a = func();
(void) a; // suppresses "unused variable" warning
assert(a != 2);

据我所知, linea = func()语句总是算作变量的使用a,而初始化不算作使用。

随着编译器的变化和诊断的改进,我不会尝试对冲未来可能出现的编译器警告,因为对冲有时会意外地抑制有效警告。

断言是如何定义的?

标准委员会和 C 实施者assert经过精心设计,不会产生虚假警告。请注意演员表的常见void程度...

  • 如果没有NDEBUG,glibcassert大致按以下方式定义(除了 以外的东西abort):

    #define assert(expr) ((expr) ? (void) 0 : abort())
    
  • 使用NDEBUG,glibc 以这种方式定义它(按照 C 标准的要求):

    #define assert(expr) ((void) 0)
    
  • 以下定义assert不符合要求,因为它没有扩展为表达式:

    #define assert(expr) { if (expr) { ... } } // wrong
    

C++ 的定义也略有不同。所以你看,assert以正确的方式定义,所以它不会产生任何虚假的编译器警告,而且它在语法上确实表现得像函数调用。

于 2012-06-22T08:13:09.497 回答
1

一般来说,不可能说一段代码永远不会从编译器收到任何警告,因为将来可能会添加新的警告,或者编译器错误可能会导致虚假警告。

我很确定 GCC 不会对if用大括号定义的空体发出警告,正是因为这很容易在有效代码中发生,例如这个(本质上与你的情况非常相似):

int a = func();
if (a == 2)
{
#ifdef SOME_BUILD_SETTING
    launch_missiles();
#endif
}

手册显示

-Wempty-body 如果在, or语句
  中出现空正文,则发出警告。此警告也由-Wextra启用。ifelsedo while

这将适用于:

#ifdef SOME_BUILD_OPTION
# define LAUNCH_MISSILES launch_missiles()
#else
# define LAUNCH_MISSILES
#endif
if (a==2)
  LAUNCH_MISSILES;

-Wextra然后在没有SOME_BUILD_OPTION定义的情况下编译。

但是使用大括号它不会发出警告,正如 Dietrich Epp 评论的那样,它不会发出警告,因为即使定义assert了它也不会扩展为空。NDEBUG

在您的代码a被初始化并使用它的值时,所以我会惊讶于它发出警告。

于 2012-06-22T08:03:33.080 回答
1

如果您assert被预处理器删除,这可能会导致问题,如下所示:

#ifdef ENABLE_ASSERT
#define assert (CONDITION) {if (!(CONDITION)) abort ();}
#else
#define assert (CONDITION) /* Nothing */
#endif

但是,如果你做得正确,那么就不会有问题:

#define assert (CONDITION) {if ((ENABLE_ASSERT) && !(CONDITION)) abort ();}

在这种情况下,编译器仍会看到在a中使用的CONDITION,但在ENABLE_ASSERT为零时会对其进行优化。让编译器优化器删除代码通常比使用预处理器更好:它避免了这样的警告,并且通常会导致代码更具可读性,并且如果有一天它变成运行时测试,则不需要重写代码.

显然,ENABLE_ASSERT必须始终定义为零或非零。

于 2012-06-22T08:24:07.593 回答
0

您可能知道assert宏是使用宏管理的NDEBUG。我认为使用 an 的东西#ifdef NDEBUG会更容易阅读并具有相同的效果。

于 2012-06-22T08:13:16.980 回答
0

是的,此代码可能会在未来某个时间点发出带有一些奇怪编译器设置的警告。

你的问题永远不会得到肯定的回答。不可能。未来是不可预测的。即使在目前,编译器标志的数量也代表着组合爆炸,很难完全分析。任何给你“是”答案的人都可能忽略了一些事情。

现在,我会说编译器通常(根据我的经验)只针对您的代码实际外观发出警告,而不是针对优化器完成后的外观发出警告。是的,如果您运行优化器,它可能能够进行更深入的分析并发现更微妙的问题。但它不会开始标记您现在多余的构造,因为优化器能够完全删除它。

所以,我认为你在这里基本上是安全的,这就像你从我这里得到的一样接近“是”。

于 2012-06-22T08:14:19.443 回答
0

两个代码块都可以,但我更喜欢:

int a = func();
assert(a != 2);
于 2012-06-22T07:41:21.670 回答