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

2

9

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.

Wellappointed answered 21/6, 2021 at 9:47 Comment(2)
You may try to use inline suppressions to disable those warnings.Resourceful
@Resourceful he might, but"ecological" mistake he makes shouldn't be ignored. Standard header inclusion never should be within extern block and inclusion of C version of headers is not recommended (formally is UB)Overlook
S
5

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
Shithead answered 22/6, 2021 at 8:8 Comment(0)
O
-1

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.

Overlook answered 21/6, 2021 at 9:59 Comment(4)
@HolyBlackCat are you saying that extern "C" { #include <stdlib.h> } is standard-compliant equivalent of #include <cstdlib> as OP implied? It's not illformed pers se (it might be) depending on implementation, if cstdlib just include stdlib.h which contains both C++ and C code and if run-time names for some symbols are different. cstdlib sometimes includes some headers from other locations or has own implementation. It's not defined.Overlook
@KamilCuk is an implementation detail, yeah, that's where I was leading. . extern "C" { #include <stdlib.h> } in C++ code, if it actually would link or not, is also implementation-specific, and I ran into linking issues when something like that was done with a couple of proprietary compilers. It's nothing to do with std namespace but with implementation-specific content of header-file where it was declaring something for compiler to use.Overlook
I assume that OP, as a sensible person, puts their includes above the extern "C" {. Then it would be valid. If you but it below, then yes, it's probably UB.Submaxillary
@Submaxillary I don't know how to interpret statement "its (header's) content wrapped in those" otherwise.I saw That before, even done by headers of very expensive GIS system, along with things like #define max #define data etcOverlook

© 2022 - 2024 — McMap. All rights reserved.