clang-tidy: How to suppress C++ warnings in C header file?

Viewed 1213

I've got a .h file that is included by both C and C++ source files. Its contents is wrapped in

#ifdef __cplusplus
extern "C" {
#endif

...

#ifdef __cplusplus
}
#endif

Yet, when I include it in a .cpp file, clang-tidy issues C++-specific messages, like

  • warning: including 'stdbool.h' has no effect in C++; consider removing it [hicpp-deprecated-headers,modernize-deprecated-headers]
  • warning: inclusion of deprecated C++ header 'stdlib.h'; consider using 'cstdlib' instead [hicpp-deprecated-headers,modernize-deprecated-headers]
  • warning: use 'using' instead of 'typedef' [modernize-use-using]

I like these checks and I want to keep them active in my clang-tidy configuration, but of course for C++ code only. I can't change the header file to use using instead of typedef or <cstdlib> instead of <stdlib.h> because it's also included by C sources, hence the extern "C".

Is there any way to tell clang-tidy to treat code in extern "C" as C instead of C++, even if included from a .cpp file?

The clang-tidy version is 12.0.0.

2 Answers

Clang-Tidy can make use of the special NOLINT or NOLINTNEXTLINE comments to suppress warning of specific lines. It is intended exactly for your use case:

  • some lines contains legacy or not stricly nice C++ code
  • there is a good reason to do so - here the code has to be parseable by a C compiler.

The higher risk when using that is to abuse it and silence warnings where it would have be possible to improve the coding. But when you need a header to be used form both C and C++ sources, and you have carefully twice read the NOLINTed line, it is perfectly fine, at least IMHO. Furthermore, it is even possible to indicate the warnings to silence:

#ifdef __cplusplus
extern "C" {
#endif
// NOLINTNEXTLINE(hicpp-deprecated-headers,modernize-deprecated-headers) C compatible code
#include <stdbool.h>
#include <stdlib.h>     // NOLINT C code requires that header
...

#ifdef __cplusplus
}
#endif

Those C and C++ headers strictly aren't equivalent for C++. Those warninggs are legal and indication of code non-compliant to C++ standard.

extern "C" only declares linkage type of declared functions and variables as global and without mangling, names are used in C format. It cannot affect object and functions that are declared with C++ features use. It have nothing to do with language's ecosystem. More of, if a C++ header appear inside those brackets, there may appear linking errors if a function declared there was originally in C++ linkage.

For C++ you have to use #ifdef __cplusplus to include C++ alternatives, including C ones otherwise.

Instead of:

#ifdef __cplusplus
extern "C" {
#endif

#include <stdbool.h>
#include <stdlib.h>

// your declarations

#ifdef __cplusplus
}
#endif

You would have a header with following structure

// headers that are universally compatible (3rd party)

#ifdef __cplusplus
#   include <cstdlib>
// other C++ headers
// Your C++ - only declarations (incompatible with C rules) 

extern "C" {
#else
#   include <stdbool.h>
#   include <stdlib.h>
// other C headers
//  C-only declarations (incompatible with C++ rules)

#endif

// your compatible declarations available for both

#ifdef __cplusplus  
}  // extern "C"
#endif

In some cases this structure would need to be adjusted because of required order of header inclusion or declarations's dependency. Blindly changing linkage of declarations in library headers you didn't write and you cannot guarantee what naming conventions and linkage type those use is a faux pas that can result in ODR breach or linking failures.

The typedef issue is annoying and you can use NOLINT or command-line option to suppress modernize-use-using as an interim measure. In general for portable header this would be done by macro-definitions at least for struct types. There is a problem where typedef and alias declaration aren't convertible in some case by standard means, e.g. consider a declaration of function pointer type.

Related