如何针对几行代码禁用GCC警告

314

1
可能是在gcc中禁用特定警告的重复问题 - 哎呀,实际上那个问题本身就是一个重复问题(但没有关闭)。这只是恰好出现在“相关”下面的那个问题。无论如何,在SO上已经问过并回答了几次了。 - Tyler McHenry
4
@paxdiablo: 我正在进行相反的操作。我已经将警告级别调高,并希望逐行排除我验证过没有问题的警告。 - Matt Joiner
7
如果你仔细检查,你会发现链接的问题中包含了一种按文件解决方案,正是我在自己的问题中提到的不令人满意的方案(我甚至偷了这个链接)。 - Matt Joiner
9
@paxdiablo,编译器有时会产生误报,有时您想使用-Werror进行编译,但不希望这些误报阻止构建。因此,在某些情况下禁用特定的情况并注释原因是有意义的。还有其他情况也可能非常有用 - 比如自动生成代码会产生无害警告,而这些警告很难去修改(因为代码是由程序生成的),尽管在这种情况下,按文件禁用更可能是解决方案。 - ideasman42
9个回答

326

看起来可以使用这种方法。我无法确定它是在哪个版本的GCC中添加的,但它应该是在2010年6月之前的某个时候。

这里有一个例子:

#pragma GCC diagnostic error "-Wuninitialized"
    foo(a);         /* error is given for this one */

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wuninitialized"
    foo(b);         /* no diagnostic for this one */
#pragma GCC diagnostic pop

    foo(c);         /* error is given for this one */
#pragma GCC diagnostic pop 

    foo(d);         /* depends on command line options */

29
一个 push 和两个 pop - 开始可能缺少另一个 push - abyss.7
49
"#pragma GCC diagnostic push #pragma GCC diagnostic pop" 是GCC编译器提供的指令,它可以记录在push指令处及之前的编译警告信息,并在pop指令处恢复到push指令时的状态。如果没有匹配的push指令,编译器将还原为命令行选项所设置的默认状态。这些功能通过GCC手册:http://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Pragmas.html 进一步解释。 - bobpaul
11
参考资料,gcc版本4.4.3支持错误/警告/忽略,但不支持push/pop。 - frankster
16
GCC有诊断push/pop功能的第一个版本是GCC 4.6.4。我通过查看GCC文档中每个版本的Diagnostic-Pragmas.html#Diagnostic-Pragmas部分确定了这一点。 - Alex Bitek
6
这个方法在实践中并不奏效,有时会产生更多的警告。或者更准确地说,对于GCC 4.7到5.1版本来说,在实践中它并不奏效。例如,可以看一下这篇文章:GCC不支持使用 'pragma GCC diagnostic' 来消除警告信息。 - jww
显示剩余6条评论

155

3
应该是能够工作的,但我的gcc-4.9却完全忽略了这一行。 - Aleksei Petrenko

56

TL;DR: 如果可以的话,避免使用,或者使用特定说明符比如_Noreturn[[nodiscard]]__attribute__,否则使用_Pragma

这是我个人博客文章在GCC和Clang中抑制警告的简短版本。

考虑以下Makefile

CPPFLAGS:=-std=c11 -W -Wall -pedantic -Werror

.PHONY: all
all: puts

用于构建以下 puts.c 源代码:

#include <stdio.h>

int main(int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}

它无法编译,因为argc未使用,并且设置是硬核的(-W -Wall -pedantic -Werror)。
你可以做以下五件事情:
  • 如果可能,改进源代码。
  • 使用属性,例如[[maybe_unused]]
  • 使用声明说明符,例如__attribute__
  • 使用_Pragma
  • 使用#pragma
  • 使用命令行选项。

改进源代码

首先应该检查源代码是否可以改进以消除警告。在这种情况下,我们不想仅仅因为这个原因改变算法,因为argc!*argv(最后一个元素后的NULL)是多余的。

使用属性,例如[[maybe_unused]]

#include <stdio.h>

int main([[maybe_unused]] int argc, const char *argv[])
{
    while (*++argv) puts(*argv);
    return 0;
}

如果你很幸运,标准可能会为你的情况提供一个属性,例如[[maybe_unused]]。属性是C2x的新功能。到目前为止,C2x定义了四个属性:[[deprecated]][[fallthrough]][[maybe_unused]][[nodiscard]]

使用声明说明符,如__attribute__

#include <stdio.h>

int main(__attribute__((unused)) int argc, const char *argv[])
{
    while (*++argv) puts(*argv);
    return 0;
}

如果你很幸运,标准会为你的情况提供一个特定的说明符,例如_Noreturn__attribute__是GCC专有扩展(也受到Clang和一些其他编译器(如armcc)的支持),许多其他编译器无法理解。如果想要可移植代码,请在宏中放置__attribute__((unused))_Pragma操作符 _Pragma可用作#pragma的替代方法。
#include <stdio.h>

_Pragma("GCC diagnostic push")
_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"")

int main(int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
_Pragma("GCC diagnostic pop")
_Pragma运算符的主要优点是,您可以将其放在宏内部,而使用#pragma指令则不可能。
缺点:它几乎是一种战术核武器,因为它基于行而不是声明工作。 _Pragma运算符是在C99中引入的。 #pragma指令。
我们可以更改源代码以抑制警告的代码区域,通常是整个函数。
#include <stdio.h>

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
int main(int argc, const char *argv[])
{
    while (*++argc) puts(*argv);
    return 0;
}
#pragma GCC diagnostic pop

缺点:它几乎像战术核武器一样,因为它是基于行而不是声明的工作。
请注意,类似的语法也存在于Clang中。
在命令行上抑制单个文件的警告
我们可以将以下行添加到Makefile中,以针对puts特别抑制警告:
CPPFLAGS:=-std=c11 -W -Wall -pedantic -Werror

.PHONY: all
all: puts

puts.o: CPPFLAGS+=-Wno-unused-parameter

这可能不是你特定情况下想要的,但它可能会帮助其他处于类似情况的读者。


3
针对“改善源代码”的问题,你也可以将main函数的声明更改为“int main(int, const char* argv[]) { ... }”。通过不给参数命名,告诉编译器它将不被使用。 - Jesse Chisholm
2
@JesseChisholm 在函数定义中省略参数名称是不允许的。请参阅ISO/IEC9899的6.9.1函数定义,§5“如果声明符包括参数类型列表,则每个参数的声明都应包括标识符[... ]”,这样代码将被gcc和clang正确地拒绝。 - Christian Hujer
5
另一种模式是将变量强制转换为 void 类型。事实上,我在一个项目中看到过以下宏定义:#define UNUSED(x) ((void)x) 用于消除警告。我想这是在 ReactOS 中使用的。 - Paul Stelian
2
__attribute__ 语法的工作方式非常巧妙,它聪明地使用双括号 (( )),因此,如果你的编译器不理解它,你可以定义 #define __attribute__(x),这些括号就会全部消失。 - doug65536
谢谢!我已经想了几年,什么时候需要使用 _Pragma() 风格而不是 #pragma 风格,现在我终于找到了!你可能想把这个加入到你的答案中:如果你想把这些调用放在宏定义内部,那么你必须使用 _Pragma() 风格!这是因为你不能从宏定义内部进行以 # 开头的预处理器调用,因为 # 字符在宏定义内部有特殊含义(它是字符串化字符),不能像那样使用。 - Gabriel Staples
@PaulStelian Windows头文件使用UNREFERENCED_PARAMETER这种方式。鉴于ReactOS受到Windows的很大“启发”,我认为可能是从那里借鉴过来的。虽然这并不是什么难以想象的事情。 - undefined

44

我知道问题是关于GCC的,但对于那些想在其他和/或多个编译器中执行此操作的人...

简而言之

你可能想看看Hedley,这是一个我编写的公共领域单一C/C++头文件,它可以为您完成很多工作。我将在本文末尾放置有关如何使用Hedley进行所有这些操作的快速部分。

禁用警告

#pragma warning (disable: …)在大多数编译器中都有相应的等价物:

  • MSVC: #pragma warning(disable:4996)
  • GCC:#pragma GCC diagnostic ignored "-W...",其中省略号是警告的名称;例如:#pragma GCC diagnostic ignored "-Wdeprecated-declarations".
  • Clang#pragma clang diagnostic ignored "-W..."。语法基本与GCC相同,许多警告名称也相同(但许多不同)。
  • Intel C++ Compiler (ICC):使用MSVC语法,但请记住警告编号完全不同。例如:#pragma warning(disable:1478 1786)
  • PGI/Nvidia:有一个diag_suppress指示: #pragma diag_suppress 1215,1444。注意,所有警告编号在20.7中增加了一次(第一个Nvidia HPC版本)。
  • TI (CCS):有一个与PGI具有相同语法(但不同的警告编号!)的diag_suppress指示:pragma diag_suppress 1291,1718
  • Oracle Developer Studio (ODS) (suncc):有一个error_messages指示。令人恼火的是,C和C++编译器的警告不同。这两个禁用基本相同的警告:
    • C:#pragma error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)
    • C++:#pragma error_messages(off,symdeprecated,symdeprecated2)
  • IAR:也像PGI和TI一样使用diag_suppress,但语法不同。一些警告编号相同,但其他警告编号已经分歧:#pragma diag_suppress=Pe1444,Pe1215
  • Pelles C:类似于MSVC,尽管数字不同:#pragma warn(disable:2241)

对于大多数编译器而言,在尝试禁用它之前检查编译器版本通常是个好主意,否则你只会触发另一个警告。例如,GCC 7添加了对-Wimplicit-fallthrough警告的支持,因此如果你关心GCC 7之前的版本,应该做如下处理:

#if defined(__GNUC__) && (__GNUC__ >= 7)
#  pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#endif

对于基于Clang的编译器,例如较新版本的XL C/C++和armclang,您可以使用__has_warning()宏来检查编译器是否知道特定的警告。

#if __has_warning("-Wimplicit-fallthrough")
#  pragma clang diagnostic ignored "-Wimplicit-fallthrough"
#endif

当然,您还需要检查是否存在__has_warning()宏:
#if defined(__has_warning)
#  if __has_warning("-Wimplicit-fallthrough")
#    pragma clang diagnostic ignored "-Wimplicit-fallthrough"
#  endif
#endif

你可能会想要做类似于{{某事}}的事情

#if !defined(__has_warning)
#  define __has_warning(warning)
#endif

所以你可以更轻松地使用__has_warning。Clang甚至在他们的手册中建议类似于__has_builtin()宏。 不要这样做。其他代码可能会检查__has_warning并在不存在时回退到检查编译器版本,如果你定义了__has_warning,你会破坏他们的代码。正确的方法是在你的命名空间中创建一个宏。例如:

#if defined(__has_warning)
#  define MY_HAS_WARNING(warning) __has_warning(warning)
#else
#  define MY_HAS_WARNING(warning) (0)
#endif

接下来你可以做像{{这样的事情}}

#if MY_HAS_WARNING(warning)
#  pragma clang diagnostic ignored "-Wimplicit-fallthrough"
#elif defined(__GNUC__) && (__GNUC__ >= 7)
#  pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
#endif

推入和弹出

许多编译器还支持将警告推入和弹出堆栈的方法。例如,以下代码将在GCC上禁用一个警告一行,然后将其返回到先前的状态:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
call_deprecated_function();
#pragma GCC diagnostic pop

当然,不同编译器对语法的约定并不一致:
- GCC 4.6+:`#pragma GCC diagnostic push` / `#pragma GCC diagnostic pop` - Clang:`#pragma clang diagnostic push` / `#pragma clang diagnostic pop` - Intel 13+(以及可能更早版本):`#pragma warning(push)` / `#pragma warning(pop)` - MSVC 15+(Visual Studio 9.0 / 2008):`#pragma warning(push)` / `#pragma warning(pop)` - ARM 5.6+:`#pragma push` / `#pragma pop` - TI 8.1+:`#pragma diag_push` / `#pragma diag_pop` - Pelles C 2.90+(以及可能更早版本):`#pragma warning(push)` / `#pragma warning(pop)`
如果我没记错的话,对于某些非常旧的GCC版本(如3.x,如果我没记错),push/pop指示必须在函数之外。
隐藏繁琐细节
对于大部分编译器,可以使用C99中引入的_Pragma来隐藏宏背后的逻辑。即使在非C99模式下,大多数编译器也支持_Pragma;唯一的例外是具有不同语法的自己的__pragma关键字的MSVC。标准的_Pragma需要一个字符串,Microsoft的版本则不需要。
#if defined(_MSC_VER)
#  define PRAGMA_FOO __pragma(foo)
#else
#  define PRAGMA_FOO _Pragma("foo")
#endif
PRAGMA_FOO

一旦预处理后,大致相当于{{某个内容}}。

#pragma foo

这让我们能够创建宏,以便我们可以编写如下的代码

MY_DIAGNOSTIC_PUSH
MY_DIAGNOSTIC_DISABLE_DEPRECATED
call_deprecated_function();
MY_DIAGNOSTIC_POP

并且在宏定义中隐藏所有丑陋的版本检查。

简单的方法:Hedley

现在你已经理解了如何在保持代码清洁的同时以可移植的方式完成这样的事情的机制,你就明白了我的一个项目Hedley的作用。你可以只包含Hedley(它是一个单一的公共领域C/C++头文件),而不必深入研究大量文档和/或安装尽可能多的编译器版本进行测试。例如:

#include "hedley.h"

HEDLEY_DIAGNOSTIC_PUSH
HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED
call_deprecated();
HEDLEY_DIAGNOSTIC_POP

将禁用有关在GCC、Clang、ICC、PGI、MSVC、TI、IAR、ODS、Pelles C以及可能其他编译器上调用已弃用函数的警告(我可能不会在更新Hedley时更新此答案)。而且,在不被认为可以工作的编译器上,宏将被预处理到无处不在,因此您的代码将继续与任何编译器一起工作。当然,HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED 不是 Hedley 所知道的唯一警告,禁用警告也不是 Hedley 可以做的全部,但希望您能理解。


感谢您创建Hedley并以如此自由的条件提供。 - undefined

25

使用:

#define DIAG_STR(s) #s
#define DIAG_JOINSTR(x,y) DIAG_STR(x ## y)
#ifdef _MSC_VER
#define DIAG_DO_PRAGMA(x) __pragma (#x)
#define DIAG_PRAGMA(compiler,x) DIAG_DO_PRAGMA(warning(x))
#else
#define DIAG_DO_PRAGMA(x) _Pragma (#x)
#define DIAG_PRAGMA(compiler,x) DIAG_DO_PRAGMA(compiler diagnostic x)
#endif
#if defined(__clang__)
# define DISABLE_WARNING(gcc_unused,clang_option,msvc_unused) DIAG_PRAGMA(clang,push) DIAG_PRAGMA(clang,ignored DIAG_JOINSTR(-W,clang_option))
# define ENABLE_WARNING(gcc_unused,clang_option,msvc_unused) DIAG_PRAGMA(clang,pop)
#elif defined(_MSC_VER)
# define DISABLE_WARNING(gcc_unused,clang_unused,msvc_errorcode) DIAG_PRAGMA(msvc,push) DIAG_DO_PRAGMA(warning(disable:##msvc_errorcode))
# define ENABLE_WARNING(gcc_unused,clang_unused,msvc_errorcode) DIAG_PRAGMA(msvc,pop)
#elif defined(__GNUC__)
#if ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406
# define DISABLE_WARNING(gcc_option,clang_unused,msvc_unused) DIAG_PRAGMA(GCC,push) DIAG_PRAGMA(GCC,ignored DIAG_JOINSTR(-W,gcc_option))
# define ENABLE_WARNING(gcc_option,clang_unused,msvc_unused) DIAG_PRAGMA(GCC,pop)
#else
# define DISABLE_WARNING(gcc_option,clang_unused,msvc_unused) DIAG_PRAGMA(GCC,ignored DIAG_JOINSTR(-W,gcc_option))
# define ENABLE_WARNING(gcc_option,clang_option,msvc_unused) DIAG_PRAGMA(GCC,warning DIAG_JOINSTR(-W,gcc_option))
#endif
#endif

这个对于GCC,ClangMSVC应该都适用。

可以使用以下方式进行调用:

DISABLE_WARNING(unused-variable,unused-variable,42)
[.... some code with warnings in here ....]
ENABLE_WARNING(unused-variable,unused-variable,42)

请参阅7个编译指示符通过编译指示符控制诊断信息编译指示符和__pragma和_Pragma关键字以获取更多详细信息。
您需要至少使用版本4.02才能在GCC中使用这些类型的编译指示符,对于MSVC和Clang的版本我不确定。
看起来,对于GCC,推送弹出编译指示符的处理有点出了问题。如果您重新启用警告,仍然会收到在DISABLE_WARNING/ENABLE_WARNING块内部的警告。对于某些版本的GCC,它可以正常工作,而对于某些版本则不能。

关于“called with”:但它们是宏吗? - Peter Mortensen

25
#pragma GCC diagnostic ignored "-Wformat"

将"-Wformat"替换为您的警告标志名称。

据我所知,无法使用push/pop语义来处理此选项。


6
很遗憾这在实践中不起作用。在某些情况下,它会产生更多的警告。或者更正确地说,对于GCC 4.7到5.1版本,在实践中它并不起作用。例如,参见 GCC不遵守 "pragma GCC diagnostic" 来消除警告 - jww

6

我曾经遇到过外部库文件的问题,比如像 ROS 的头文件。我喜欢在 CMakeLists.txt 中使用以下选项来进行更严格的编译:

set(CMAKE_CXX_FLAGS "-std=c++0x -Wall -Wextra -Wstrict-aliasing -pedantic -Werror -Wunreachable-code ${CMAKE_CXX_FLAGS}")

然而,这样做会导致在外部引入的库中出现各种各样的严谨错误。解决方法是在包含外部库之前禁用所有严谨警告,并像这样重新启用它们:

// Save compiler switches
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"

// Bad headers with a problem goes here
#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>

// Restore compiler switches
#pragma GCC diagnostic pop

2
这个问题最好用gcc的systemdirectories来处理,不是吗? - Red XIII
1
@RedXIII - 是的,如果你能够列出这些目录并在gcc命令行中指定,那么这是一个选项。然而,很多时候编译器会在管道深处被调用,或者你对别人如何编译你的代码没有太多控制。在这些情况下,上述方法可能是更好的解决方案。 - Shital Shah

4

以下是在IAR中完成此操作的方法。请尝试:

#pragma diag_suppress=Pe177
void foo1(void)
{
   /* The following line of code would normally provoke diagnostic 
      message #177-D: variable "x" was declared but never referenced.
      Instead, we have suppressed this warning throughout the entire 
      scope of foo1(). 
   */
   int x;
}
#pragma diag_default=Pe177

参考官方文档


2
与其消除警告,GCC风格通常是使用标准的C结构或使用__attribute__扩展来向编译器传达更多关于您意图的信息。
例如,有关将赋值用作条件的警告可以通过将赋值放在括号中来抑制,即if ((p=malloc(cnt)))而不是if (p=malloc(cnt))。
有关未使用的函数参数的警告可以通过某些奇怪的__attribute__(我经常忘记)或自我分配等方式来抑制。
但通常我更喜欢全局禁用任何会对正确代码产生警告的警告选项。

2
也许是这样。我的意图不是证明任何一般的情况模式,而是观察gcc在警告抑制方面的哲学。 - R.. GitHub STOP HELPING ICE
编译器在添加括号后对警告的行为不同?!??!!哇!这真是出乎意料。 - Jason S
2
@JasonS,括号不会改变编译器对警告的行为,它只是改变语句的语义。额外的括号使编译器完成赋值并将其最终值作为表达式保留,这不应该引发警告。如果您想要清晰明了,可以说if ((p=malloc(cnt)) != NULL) ...,因为这就是编译器在幕后所做的。 - Jesse Chisholm
1
@JesseChisholm:我认为你的解释不准确。 - R.. GitHub STOP HELPING ICE
你所说的 "ICE" 是什么?也许可以在你的个人资料中详细说明一下? - Peter Mortensen
1
ICE = 美国移民和海关执法局 - Peter Mortensen

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