PDS: Compiler Warnings

A blog about C++ programming, more or less.

Compiler warnings are messages produced by a compiler who is trying to warn you about potential issues in your code. Although, warnings don’t interrupt the compilation process, they should not just be ignored, as warnings may provide valuable insight and advice to keep your programs away from certain bugs. Paying attention to compiler warnings and keeping a low number of warnings is a commonly used preemptive technique to prevent bugs in the earlier phase of software development life circle. As it is said, it is much easier and cheaper to fix bugs in earlier stages than later ones.

Enable warnings

Compiler warnings are important, but most useful warnings are not enabled by default. To turn on more warnings, you need to set compiler-specific flags. Here is a concise reference of some common warning flags:[1]

For the complete list of all the warning flags, please consult your compiler’s manuals.

Warnings in moderation

To quote Oscar Wilde:

everything in moderation, including moderation.

This piece of advice applies to compiler warnings as well. Because, each warning diagnostic that is enabled for a project will consume certain amount of development resources to maintain, and having too many warnings enabled will soon become a burden of its own. This is especially true for -Weverything. Thus, using of -Weverything is generally not recommended. It should only be used as a way to find the names of interesting warning flags.[4]

Which warnings should be enabled? That certainly depends on your use cases. In general, start with -Wall and -Wextra is a good choice for most projects. Enable -Wpedantic if you are aiming for ISO C/C++ conformance. Then, gradually add individual warning that you found useful by combining the -W prefix with the warning name. For a list of other useful warning options, which are not included in -Wall or -Wextra, please refer to the reference material [5].

Warnings as errors

One way to enforce a zero warning policy for your project is turning all the warnings into errors by using the -Werror flag. Which means you have to clear all warnings to make your program compile. But, be aware, this approach does introduce a new issue, as using -Werror also means that changing or updating your compiler is probably more difficult. In general, it is advised to make -Werror optional by wrapping it with a build option.

Warnings and CMake

For modern CMake, the right way to enable compiler warnings should be something like this:

set(COMPILER_WARNING_OPTIONS -Wall -Wextra)
if (WARNINGS_AS_ERRORS)
    list(APPEND COMPILER_WARNING_OPTIONS -Werror)
endif ()

target_compile_options(my_target PRIVATE ${COMPILER_WARNING_OPTIONS})

Here, WARNINGS_AS_ERRORS is a build option to turn warnings into errors. my_target is the name of the CMake target whose warnings you want to enable, this target must have been created by a command such as add_executable() or add_library() and must not be an ALIAS target.[6] PRIVATE limits those flags to this target only and prevents them from affecting projects linking to your target.

Suppress warnings

Warnings are warnings for reasons, which is because they may produce false positive results. In such cases, you can either explicitly disable the offending diagnostic as a whole with its corresponding -Wno- option if it produces too much noise, or suppress each occurrence with certain techniques. However, before doing that, you should make sure those warnings are indeed not caused by incorrect code.

Suppress unused parameter warnings

-Wunused-parameter or -Wunused-but-set-parameter could be useful to prevent bugs which are results of parameter names that are easily misread, a silly example would be:

auto Plus(const int x0, const int xO) {
    return x0 + x0;
}

Most likely, the above function was intended to return the sum of the given two parameters, instead of the double of only the first parameter.

However, there are cases where unused parameters cannot be avoid, such as functions that have to conform to certain predefined interfaces. In that case, the recommended way to suppress this kind of warnings is to make unused parameters unnamed.[7:§F.9]

#include <csignal>

extern "C" void ExitSignalHandler(int /*signal*/) {
}

Additionally, if parameters are conditionally unused, declare them with the [[maybe_unused]] attribute. For example:[7:§F.9]

template<typename T>
auto Pad(const T v, [[maybe_unused]] const int extra = sizeof(int)) {
    if constexpr (sizeof(T) < sizeof(long)) {
        return v + extra;
    }
    return v;
}

Suppress unused variable warnings

Similar to the unused parameter warnings, -Wunused-variable may help you to detect issues those are related to unintentionally accessing the wrong variables. For example:

inline void UnusedVariableWarning() {
    const auto N = 5;
    int i = 0;
    for (int i = 0; i < N; ++i) {
    }
}

The first i is never used, as it is shadowed by the second i. But, sometimes, it is possible that some variables are intentionally not used in all build targets, like, the following use of assert():

#include <cassert>

void UnusedVariableNoWarning() {
    [[maybe_unused]] const auto x = sizeof(char);
    assert(x);
}

As you can see, [[maybe_unused]] can be used to suppress unused variable warnings as well.

Suppress unused function warnings

Like the unused variable warnings, -Wunused-function could be helpful from time to time, and the [[maybe_unused]] attribute can also be useful here if you really want to suppress the unused function warnings for a specific set of functions.

[[maybe_unused]] static void UnusedFunctionNoWarning() {
}

Suppress unused result warnings

-Wunused-result is given when you ignore the return value of a function that is declared with attribute [[nodiscard]]. The following function as an example:

[[nodiscard]] auto connect() {
    int error_code = -1;
    return error_code;
}

The [[nodiscard]] here is trying to encourage the callers to remember to test the returned error code, as the connect() may fail. Usually, there is a good reason why the author of the function used [[nodiscard]] in the first place, so think twice before you discard such a result. If you still think it’s appropriate and your code reviewer agrees, use std::ignore = to turn off the warning. This way is simple, portable, and easy to grep.[7:§ES.48]

#include <tuple>

std::ignore = connect();

Suppress switch warnings

The -Wswitch warnings warn you about enumeration values that are not handled in a switch statement. This kind of warnings usually happens when you added a new enumeration value to an enumerated type, but forgot to add a corresponding case label to the switch statement. For example:

enum class MyEnum {
    one,
    two,
    three,
};

void Dispatch(const MyEnum e) {
    switch (e) {
    case MyEnum::one:
        break;
    case MyEnum::two:
        break;
    }
}

Although, it is easy to suppress this warning with a default label, but it is not recommended, as that will also suppress any diagnostics which may detect genuine mistakes in the future. In general, it is better to just add the missing enumeration value case labels explicitly.

Suppress implicit fallthrough warnings

-Wimplicit-fallthrough warnings could be useful in case you forgot to add a break for one case label before reaching the next one. If you are sure that is intended, you can suppress this warning with [[fallthrough]].

auto FallThroughNoWarning(const int n) {
    int result = 0;

    switch (n) {
    case 1:
        result = 1;
        [[fallthrough]];
    default:
        ++result;
    }

    return result;
}

Conclusion

In today’s post, I have talked about the importance of enabling the right set of compiler warnings in order to reduce the number of defects in your code base. I have also showed a few commonly used techniques to suppress certain warnings when necessary. I hope you found this post useful, if so, you may also want to check out other articles which also belong to the PDS series.

References

  1. Using the GNU Compiler Collection (GCC): Options to Request or Suppress Warnings
  2. Tutorial: Managing Compiler Warnings with CMake by Jonathan Müller
  3. Clang Compiler User’s Manual
  4. Don’t put -Weverything in your build flags by Arthur O’Dwyer
  5. Useful GCC warning options not enabled by -Wall -Wextra by Krister Walfridsson
  6. CMake Documentation: cmake-commands: target_compile_options
  7. C++ Core Guidelines by Bjarne Stroustrup and Herb Sutter