NELKINDA SOFTWARE CRAFT

Suppressing Warnings in GCC and Clang

There are at least 7 ways of how to suppress warnings in GCC and Clang. This article explains these 7 ways, which are writing different code, qualifiers and specifiers, attributes ([[ ]]), __attribute__, _Pragma, #pragma, and command line options.
TL;DR: If possible, write better code, otherwise, if possible, use qualifiers and specifiers, [[ ]], or __attribute__, else use _Pragma.

Author:
Christian Hujer, Software Crafter and CEO / CTO of Nelkinda Software Craft Private Limited
First Published:
by NNelkinda Software Craft Private Limited
Last Modified:
by Christian Hujer
Approximate reading time:
Figure -1: GCC in Konsole showing a compiler warning

1 Warnings

Ideally, we will write our source code in a way that it would not generate any compiler warnings. There are, however, some situations in which this is not possible. To name just a few:

There are at least 7 different ways how to deal with compiler warnings in GCC and Clang:

2 Sample Project

I want to showcase all 7 different ways how to deal with compiler warnings in GCC and Clang. For that, I will use the following sample project.

CPPFLAGS:=-std=c11 -W -Wall -pedantic -Werror
CFLAGS:=-O2 -save-temps

.PHONY: all
all: puts

.PHONY: clean
clean::
	$(RM) *.[adios] puts
Listing 2-1: Makefile for building the sample project
#include <stdio.h>

int main(int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
Listing 2-2: puts.c sample source code

When compiling using make, we get the following error message:

$ make
cc -O2 -save-temps -std=c11 -W -Wall -pedantic -Werror   puts.c   -o puts
puts.c: In function ‘main’:
puts.c:3:14: error: unused parameter ‘argc’ [-Werror=unused-parameter]
 int main(int argc, const char *argv[])
              ^~~~
cc1: all warnings being treated as errors
<builtin>: recipe for target 'puts' failed
make: *** [puts] Error 1
Listing 2-3: Output of running make

The parameter argc of function main is not used. We can, however, not drop it, as this would be against the C and POSIX specifications. And both, GCC and Clang, know that. You would get -Werror=main warnings if you try to remove argc from the signature.

3 Addressing Warnings

3.1 Writing Different Source Code

One way to avoid warnings is to write different source code. In general, avoiding warnings by writing better source code is the preferable way. But note the subtle difference between the words different and better. Source code is not necessarily better just because it's different, even if it produces fewer warnings. This is an excellent example for one of those rare situations where different with fewer warnings does not mean better.

#include <stdio.h>

int main(int argc, const char *argv[])
{
    for (int i = 1; i < argc; i++)
        puts(argv[i]);
    return 0;
}
Listing 3-1: puts.c with different loop that uses both variables

This compiles just fine with make. But the for-loop is more complex than the previous while loop. And at least on an AMD64 instruction set, GCC and Clang will emit bigger, more complex code. Discussing the details of this is a story for a different time. But in this case, the for loop is not better than the while loop. We've made the code worse just to get rid of a compiler warning, which is not good.

If your code gets better when getting rid of a compiler warning, changing the source code is the way to go. If you can't find a way of how to get rid of a compiler warning by making the code better, you need other options.

Writing different source code is a potential risk. How do you know that the different code behaves the same way as the old code? You could possibly introduce bugs, so be careful. Best, have tests which will cover your change.

3.2 Suppressing Warnings by Giving the Compiler more Information

As we could see, changing the source code does not always lead to better solutions. Other approaches are required.

C compilers cannot have a holistic knowledge and understanding of a software project. The feedback they give is based on the input files only, nothing else. Some compiler warnings would go away if we would give the compiler more information.

3.2.1 Standard Qualifiers and Specifiers

The C language defines a number of specifiers which programmers can use to give the compiler more information. const and _Noreturn are such examples. By taking a closer look at the C language, you may discover a keyword that's use will do two things: Make your source code more correct, and because of that, also remove the warning that you're looking at.

You can read more about const, _Noreturn and other qualifiers and specifiers in the Programming Languages — C specification.

3.2.2 Standard C++11 / C2x Attributes [[ ]]

In the latest version C2x, the standard added a way for specifying attributes, as well as a set of predefined standard attributes. These are: [[deprecated]], [[fallthrough]], [[maybe_unused]], and [[nodiscard]].

Attributes will not really suppress a warning. With attributes, we can give the compiler information to understand certain aspects of our source code better. This better understanding can make a warning go away.

In this case, we want to tell the compiler that the parameter argc is not used. So we add [[maybe_unused]] to the declaration of the parameter argc.

#include <stdio.h>

int main([[maybe_unused]] int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
Listing 3-2: puts.c avoiding the warning using the [[maybe_unused]] attribute

If an attribute is used frequently, it can be useful to declare a macro for it. The macro can also be useful if you need to deal with different compilers and some of them do not support the C2x attribute syntax. I name the macro A_Unused and think of it in a similar way as annotations in languages like Java.

#include <stdio.h>

#define A_Unused [[maybe_unused]]

int main(A_Unused int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
Listing 3-3: puts.c with a macro for [[maybe_unused]]

You can read more about attributes in the Programming Languages — C specification.

3.2.3 __attribute__

The information that we want to give to the compiler may not (yet) be supported by the C standard. Compilers can implement their own ways of how to provide additional information. In the case of GCC and Clang, that is __attribute__.

__attribute__ will not really suppress a warning. With __attribute__, we can give GCC and Clang information to understand our source code better. And with that better understanding, a warning can go away.

In this case, we want to tell GCC and Clang that the parameter argc is not used. So we add __attribute__((unused)) to the declaration of the parameter argc.

#include <stdio.h>

int main(__attribute__((unused)) int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
Listing 3-4: puts.c avoiding the warning using __attribute__((unused))

If an attribute is used frequently, it can be useful to declare a macro for it. The macro can also be useful if you need to deal with different compilers and some of them do not support __attribute__. I name the macro A_Unused and think of it in a similar way as annotations in languages like Java.

A macro can also be useful in case you want to write portable code. Other compilers might not understand __attribute__ and trip over it. Defining the macro empty for other compilers can help writing portable code.

#include <stdio.h>

#define A_Unused __attribute__((unused))

int main(A_Unused int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
Listing 3-5: puts.c with a macro for __attribute__((unused))

You can read more about __attribute__ in Using the GNU Compiler Collection (GCC) and the Clang Compiler User's Manual.

3.2.4 Combined approach for Multiple Language Versions and Compilers

The previous approaches can be combined to support multiple compilers. It should be easy to extend for compilers that use a syntax not mentioned here, like __declspec.

#include <stdio.h>

#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000L
#define A_Unused [[maybe_unused]]
#elif defined(__GNUC__)
#define A_Unused __attribute__((unused))
#else
#define A_Unused
#endif

int main(A_Unused int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
Listing 3-6: puts.c with a macro for [[maybe_unused]] and __attribute__((unused))

3.3 Suppressing Warnings by Controlling the Diagnostic Stack

GCC and Clang have a diagnostic stack which contains the configuration for diagnostics. Which warnings and errors to show is part of that configuration. It is possible to save the diagnostics configuration state, change it, and then restore it. That it's a stack means that this can be nested. The diagnostic stack is controlled with pragmas. The C language supports two types of pragmas:

They both work pretty much the same way, just the syntax of how to get it into the C file is different. The expression-like _Pragma() is newer, it was introduced in C99.

3.3.1 #pragma Directive

#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
Listing 3-7: puts.c using the #pragma directive for warning suppression

You might wonder if you need to replace GCC with clang in case you use Clang instead of GCC. No, you needn't. Clang understands these GCC pragmas just fine. You could replace clang with GCC. But clang only works for Clang, and GCC works for both, GCC and Clang.

3.3.2 _Pragma() Operator

The _Pragma() operator was added to the C standard for version C99 (ISO/IEC 9899:1999). Because it is an operator, it can be used in macros.

#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")
Listing 3-8: puts.c using the _Pragma operator for warning suppression

3.3.3 _Pragma() Operator with Macro

#include <stdio.h>

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

#define A_Pop \
    _Pragma("GCC diagnostic pop") \

A_IgnoreUnused
int main(int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
A_Pop
Listing 3-9: puts.c using a macro for the _Pragma operator

3.4 Suppressing per file in the Makefile

You can also suppress warnings in the Makefile. This can be done on a per-file basis. More generally speaking, you can alter variables on a per-file basis. The variable CPPFLAGS controls the command-line options for the C preprocessor and compiler.

CPPFLAGS:=-std=c11 -W -Wall -pedantic -Werror
CFLAGS:=-O2 -save-temps

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

.PHONY: all
all: puts

.PHONY: clean
clean::
	$(RM) *.[adios] puts
Listing 3-10: Makefile that suppresses the warning for one file.
#include <stdio.h>

int main(int argc, const char *argv[])
{
    while (*++argv)
        puts(*argv);
    return 0;
}
Listing 3-11: puts.c sample source code

4 Comparison of Approaches

We've seen different approaches to addressing compiler warnings. Let's recap and summarize.

The best approach, if you have tests, is to refactor the code to be even better and not have warnings. If you do not have tests, be aware of the risks of that approach. The second-best approach is to give the compiler more information.

Writing Different CodeGive the compiler more infoActually suppress
specifiers like _Noreturnstandard attributes [[ ]]__attribute___Pragma#pragmaCommand Line
Macro capablen/a
Scopewarningdeclarationlinesfile
RiskTestslowlowlowlowmediummediumhigh
No Testshigh
Sincealwaysdepends on specifierC2x / depends on attributeimplementation definedC99 / implementation definedC90 / implementation definedimplementation defined

4.1 Source on GitHub

The source code of all examples is available on GitHub.