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

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.

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

DRY principle for extern "C" like #pragma once [duplicate]

Why shouldn’t extern "C" be specified for a function that needs to be defined as a C function? What effect would that have on the compiler when compiling the file as a C source?
If there is no effect on the C compiler, can’t we just define a function in a header file as below by removing the #ifdef __cplusplus check?
extern "C" {
int MyFunc();
}
An answer to another question says that the #ifdef is needed, but I don’t understand why:
Regarding #2: __cplusplus will be defined for any compilation unit that is being run through the C++ compiler. Generally, that means .cpp files and any files being included by that .cpp file. The same .h (or .hh or .hpp or what-have-you) could be interpreted as C or C++ at different times, if different compilation units include them. If you want the prototypes in the .h file to refer to C symbol names, then they must have extern "C" when being interpreted as C++, and they should not have extern "C" when being interpreted as C -- hence the #ifdef __cplusplus checking.
The construct extern "C" is a C++ construct and is not recognized by a C compiler. Typically, it will issue a syntax error message.
A common trick is to define a macro, for example EXTERN_C, that would expand to different thing depending on if you compile using C or C++. For example:
In a common header file:
#ifdef __cplusplus
#define EXTERN_C extern "C" {
#define EXTERN_C_END }
#else
#define EXTERN_C
#define EXTERN_C_END
#endif
In other files:
EXTERN_C
int MyFunc(void);
EXTERN_C_END
If you compile a source file as C, it will not recognize extern "C", and would usually result in a compilation error.
If you compile a source file as C++, it will recognize extern "C", and the correct names will be linked.
Therefore, you can only use it reliably to specify C symbol names for files you compile as C++.
If you compile sources as C and C++, or your interfaces are intended for C and C++ clients, you would need to specify this one way or another in order for your clients to get the correct symbols when linking (and so on).
Related: You are allowed to write extern "C++" - for C++ translations.

Linking to C libraries from C++: why isn't extern always needed?

Usually to get a C library working from C++ you have to include it with extern "C" { #include <clibrary.h> }. Many libraries will include in their header files code like #ifdef __cplusplus extern "C" { ... to make them more user friendly to C++ code (e.g. pthread.h). Sometimes this is not the case. For instance, stdio.h has no such #ifdef, yet I can still compile & link the usual #include <stdio.h> int main() {printf("Hello");} using a C++ compiler without wrapping it in an extern "C" statement. Why is this?
Usually to get a C library working from C++ you have to include it with extern "C" { #include <clibrary.h> }.
Only when the library was not designed with C++ compatibility in mind. But this is a hack.
Many libraries will include in their header files code like #ifdef __cplusplus extern "C" { ... to make them more user friendly to C++ code (e.g. pthread.h)
Yes, a good library will do this.
As a result, you do not need to and should not add another extern "C" around the #include.
stdio.h is an example of a header that will be doing this properly (see below).
For instance, stdio.h has no such #ifdef
Sure it does! Follow the money trail…
why isn't extern always needed?
So, in conclusion, you only need to do this yourself when the author of the header file didn't do it for you. When the author of the header file did it, you do not need to do it.
For instance, stdio.h has no such #ifdef
It probably does. Regardless, <stdio.h> is a header provided by the C++ standard library (inherited from the C standard library). It is guaranteed to work without extern "C" as are all standard headers.
Note that the usage of <name.h> name of the inherited standard headers in C++ instead of <cname> is deprecated in the current edition of the standard and has been identified as a candidate for removal in future revisions.
why isn't extern always needed?
Simply because some headers have been written to support C++ directly, and so do it by themselves.

How non standard c headers (unistd.h, fcntl.h, ...) work with c++ [duplicate]

Do I need an extern "C" {} block to include standard C headers in a C++ program. Only consider standard C headers which do not have counterparts in C++.
For example:
extern "C" {
#include <fcntl.h>
#include <unistd.h>
}
The system C headers usually already include a extern "C" block, guarded by #ifdef __cplusplus. This way the functions automatically get declared as extern "C" when compiled as C++ and you don't need to do that manually.
For example on my system unistd.h and fcntl.h start with __BEGIN_DECLS and end with __END_DECLS, which are macros defined in sys/cdefs.h:
/* C++ needs to know that types and declarations are C, not C++. */
#ifdef __cplusplus
# define __BEGIN_DECLS extern "C" {
# define __END_DECLS }
#else
# define __BEGIN_DECLS
# define __END_DECLS
#endif
The behavior of <fcntl.h> and <unistd.h> in C++ is not specified by the standard (because they are also not part of the C89 standard). That said, I have never seen a platform where they (a) exist and (b) actually need to be wrapped in an extern "C" block.
The behavior of <stdio.h>, <math.h>, and the other standard C headers is specified by section D.5 of the C++03 standard. They do not require an extern "C" wrapper block, and they dump their symbols into the global namespace. However, everything in Annex D is "deprecated".
The canonical C++ form of those headers is <cstdio>, <cmath>, etc., and they are specified by section 17.4.1.2 (3) of the C++ standard, which says:
<cassert> <ciso646> <csetjmp> <cstdio> <ctime> <cctype> <climits>
<csignal> <cstdlib> <cwchar> <cerrno> <clocale> <cstdarg> <cstring>
<cwctype>
Except as noted in clauses 18 through 27, the contents of each header
cname shall be the same as that of the corresponding header name.h, as
specified in ISO/IEC 9899:1990 Programming Languages C (Clause 7), or
ISO/IEC:1990 Programming Languages—C AMENDMENT 1: C Integrity, (Clause
7), as appropriate, as if by inclusion. In the C++ Standard Library,
however, the declarations and definitions (except for names which are
defined as macros in C) are within namespace scope (3.3.5) of the
namespace std.
So the standard, non-deprecated, canonical way to use (e.g.) printf in C++ is to #include <cstdio> and then invoke std::printf.
Yes, you do. However, many systems (notably Linux) are already adding an extern "C" bracketing like you do. See (on Linux) files /usr/include/unistd.h /usr/include/features.h and the macro __BEGIN_DECLS defined in /usr/include/sys/cdefs.h and used in many Linux system include files.
So on Linux, you usually can avoid your extern "C" but it does not harm (and, IMHO, improve readability in that case).
No, you should use the C++ wrapper headers (for instance like <cstdio>). Those take care of all that for you.
If it's a header that doesn't have those, then yes, you'll want to wrap them in extern "C" {}.
ETA: It's worth noting that many implementations will include the wrapper inside the .h file like below, so that you can get away with not doing it yourself.
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
the macro __BEGIN_DECLS defined in /usr/include/sys/cdefs.h and used in many Linux system include files.
It is a good idea to let the compiler know so that it can expect C code when compiling as C++. You might also find that the header files themselves contain extern "C" { as guards.
For example, curses.h on my system contains:
#ifdef __cplusplus
extern "C" {
...
I just double checked the stdlib.h for the GNU compiler and the declarations do not use extern "C" as declarations.
edit:
if defined __cplusplus && defined _GLIBCPP_USE_NAMESPACES
define __BEGIN_NAMESPACE_STD namespace std {
So including the old headers will place declarations on std provided _GLIBCPP_USE_NAMESPACES is defined?

How to use C code in C++

Just a small question:
Can C++ use C header files in a program?
This might be a weird question, basically I need to use the source code from other program (made in C language) in a C++ one. Is there any difference between both header files in general? Maybe if I change some libraries...
I hope you can help me.
Yes, you can include C headers in C++ code. It's normal to add this:
#ifdef __cplusplus
extern "C"
{
#endif
// C header here
#ifdef __cplusplus
}
#endif
so that the C++ compiler knows that function declarations etc. should be treated as C and not C++.
If you are compiling the C code together, as part of your project, with your C++ code, you should just need to include the header files as per usual, and use the C++ compiler mode to compile the code - however, some C code won't compile "cleanly" with a C++ compiler (e.g. use of malloc will need casting).
If on, the other hand, you have a library or some other code that isn't part of your project, then you do need to make sure the headers are marked as extern "C", otherwise C++ naming convention for the compiled names of functions will apply, which won't match the naming convention used by the C compiler.
There are two options here, either you edit the header file itself, adding
#ifdef __cplusplus
extern "C" {
#endif
... original content of headerfile goes here.
#ifdef __cplusplus
}
#endif
Or, if you haven't got the possibility to edit those headers, you can use this form:
#ifdef __cplusplus
extern "C" {
#endif
#include <c_header.h>
#ifdef __cplusplus
}
#endif
Yes, but you need to tell the C++ compiler that the declarations from the header are C:
extern "C" {
#include "c-header.h"
}
Many C headers have these included already, wrapped in #if defined __cplusplus. That is arguably a bit weird (C++ syntax in a C header) but it's often done for convenience.

Could we use extern "C" in C file without #ifdef __cplusplus?

Why shouldn’t extern "C" be specified for a function that needs to be defined as a C function? What effect would that have on the compiler when compiling the file as a C source?
If there is no effect on the C compiler, can’t we just define a function in a header file as below by removing the #ifdef __cplusplus check?
extern "C" {
int MyFunc();
}
An answer to another question says that the #ifdef is needed, but I don’t understand why:
Regarding #2: __cplusplus will be defined for any compilation unit that is being run through the C++ compiler. Generally, that means .cpp files and any files being included by that .cpp file. The same .h (or .hh or .hpp or what-have-you) could be interpreted as C or C++ at different times, if different compilation units include them. If you want the prototypes in the .h file to refer to C symbol names, then they must have extern "C" when being interpreted as C++, and they should not have extern "C" when being interpreted as C -- hence the #ifdef __cplusplus checking.
The construct extern "C" is a C++ construct and is not recognized by a C compiler. Typically, it will issue a syntax error message.
A common trick is to define a macro, for example EXTERN_C, that would expand to different thing depending on if you compile using C or C++. For example:
In a common header file:
#ifdef __cplusplus
#define EXTERN_C extern "C" {
#define EXTERN_C_END }
#else
#define EXTERN_C
#define EXTERN_C_END
#endif
In other files:
EXTERN_C
int MyFunc(void);
EXTERN_C_END
If you compile a source file as C, it will not recognize extern "C", and would usually result in a compilation error.
If you compile a source file as C++, it will recognize extern "C", and the correct names will be linked.
Therefore, you can only use it reliably to specify C symbol names for files you compile as C++.
If you compile sources as C and C++, or your interfaces are intended for C and C++ clients, you would need to specify this one way or another in order for your clients to get the correct symbols when linking (and so on).
Related: You are allowed to write extern "C++" - for C++ translations.