gcc 优化

Posted

技术标签:

【中文标题】gcc 优化【英文标题】:gcc optimizations 【发布时间】:2012-06-22 07:38:47 【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

据我所见,gcc 不会发出警告,说明优化器运行后代码会发生什么。而且我认为实际上这样做是错误的。警告的目的不是警告您优化器使您在代码中所做的事情变得多余。 @LuchianGrigore 是的,我已经尝试使用 -0s -O3 等。编译成功。但我不确定是否存在一些能够使构建失败的随机配置。 @Omnifarious gcc 由于更积极的优化,肯定会发出额外的警告。请参阅here 了解更多信息。 @juanchopanza,那本书已经过时了,-Wuninitialized 现在在-O0 上启用(但效果不是很好,所以仍然不会对该代码发出警告,所以你的观点持有) @juanchopanza - 点头 我的观点比这更微妙一些。是的,优化器会给你更好的质量警告。但这些警告与优化器完成对您的代码的操作后代码的外观无关。它们是关于它在进行更深入的分析后在您最初拥有的代码中发现的东西。 【参考方案1】:

根据我的测试,以下代码会生成带有-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);

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

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

断言是如何定义的?

标准委员会和 C 实现者已经仔细设计了 assert,因此它不会产生虚假警告。请注意void 的常见转换是...

没有NDEBUG,glibc 大致以以下方式定义assert(除了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 的定义方式恰到好处,因此它不会产生任何虚假的编译器警告,而且它在语法上的行为确实像函数调用。

【讨论】:

【参考方案2】:

如果您的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 必须始终定义为零或非零。

【讨论】:

NDEBUG定义的C标准(7.2/1)需要#define assert(ignore) ((void)0) 此外,assert必须扩展为 void 表达式,因此不允许兼容的实现使用 if 语句来实现它。 @DietrichEpp:如果这对您的代码来说是一个真正的问题,您可以使用内联函数而不是宏。编译器将同样优化它。 @JonathanWakely:好吧,这没什么用! @ams:assert 宏的定义早于inline 关键字的存在。遗留代码可能使用#ifdef assert 之类的东西,因此将其定义为函数会破坏旧代码。他们是休息时间。【参考方案3】:

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

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

int a = func();
if (a == 2)

#ifdef SOME_BUILD_SETTING
    launch_missiles();
#endif

说明书显示

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

申请者:

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

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

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

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

【讨论】:

if (a == 2) assert(false); 永远不会产生空正文警告,即使是 NDEBUG。当NDEBUG 被定义时,assert(false) 将被扩展为(void) 0,即使它什么也不做,它也算作一个非空体。 好点,我应该准确地测试该代码,而不是近似值。现已修复,谢谢。【参考方案4】:

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

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

【讨论】:

如果他不重用a,他甚至可以重用assert(func() != 2); @olchauvin:当断言被禁用时,这在发布模式下的行为会有所不同 - func() 根本不会被调用。但是 func 可能会做一些事情(可能会增加调用计数器) @Andrew 代码中没有任何内容表明断言在发布模式下被禁用。 @Andrew 你是对的。我错误地认为这是一个根本不会在发布中放入的单元测试。【参考方案5】:

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

【讨论】:

【参考方案6】:

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

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

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

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

【讨论】:

以上是关于gcc 优化的主要内容,如果未能解决你的问题,请参考以下文章

禁用 GCC 中的所有优化选项

《编译 - 编译杂记》GCC优化等级说明

如果我设置优化-O3,C++ 整个程序优化是不是在 gcc 中完成?

GCC 优化级别有多少?

如何在 gcc 中启用单个优化标志?

GCC 优化标志