Combining C++ and C - How Does #Ifdef _Cplusplus Work

Combining C++ and C - how does #ifdef __cplusplus work?

extern "C" doesn't really change the way that the compiler reads the code. If your code is in a .c file, it will be compiled as C, if it is in a .cpp file, it will be compiled as C++ (unless you do something strange to your configuration).

What extern "C" does is affect linkage. C++ functions, when compiled, have their names mangled -- this is what makes overloading possible. The function name gets modified based on the types and number of parameters, so that two functions with the same name will have different symbol names.

Code inside an extern "C" is still C++ code. There are limitations on what you can do in an extern "C" block, but they're all about linkage. You can't define any new symbols that can't be built with C linkage. That means no classes or templates, for example.

extern "C" blocks nest nicely. There's also extern "C++" if you find yourself hopelessly trapped inside of extern "C" regions, but it isn't such a good idea from a cleanliness perspective.

Now, specifically regarding your numbered questions:

Regarding #1: __cplusplus will stay defined inside of extern "C" blocks. This doesn't matter, though, since the blocks should nest neatly.

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.

To answer your question #3: functions without prototypes will have C++ linkage if they are in .cpp files and not inside of an extern "C" block. This is fine, though, because if it has no prototype, it can only be called by other functions in the same file, and then you don't generally care what the linkage looks like, because you aren't planning on having that function be called by anything outside the same compilation unit anyway.

For #4, you've got it exactly. If you are including a header for code that has C linkage (such as code that was compiled by a C compiler), then you must extern "C" the header -- that way you will be able to link with the library. (Otherwise, your linker would be looking for functions with names like _Z1hic when you were looking for void h(int, char)

5: This sort of mixing is a common reason to use extern "C", and I don't see anything wrong with doing it this way -- just make sure you understand what you are doing.

Why is __cplusplus defined within extern C

The compiler outputs the following:

In file included from /usr/include/c++/4.8.2/bits/stl_algobase.h:61:0,
from /usr/include/c++/4.8.2/bits/stl_tree.h:61,
from /usr/include/c++/4.8.2/map:60,
from /usr/include/openmpi-x86_64/openmpi/ompi/mpi/cxx/mpicxx.h:38,
from /usr/include/openmpi-x86_64/mpi.h:2674,
from x1.cpp:6:
/usr/include/c++/4.8.2/bits/cpp_type_traits.h:72:3: error: template with C linkage
template<typename _Iterator, typename _Container>
^
/usr/include/c++/4.8.2/bits/cpp_type_traits.h:85:3: error: template with C linkage
template<bool>
^
...

The mpi.h header detects that it's being compiled as C++ and so includes C++ specific features. However templates (among other things) don't work with C linkage (i.e. if the header is within an extern "C" block).

Move the include above extern "C":

#include <mpi.h>

#ifdef __cplusplus
extern "C" {
#endif

void library_do(MPI_Comm comm);

#ifdef __cplusplus
}
#endif

__cplusplus compiler directive defined and not defined

Look here: Combining C++ and C — how does #ifdef __cplusplus work?

extern "C" doesn't really change the way that the compiler reads the
code. If your code is in a .c file, it will be compiled as C, if it is
in a .cpp file, it will be compiled as C++ (unless you do something
strange to your configuration).

What extern "C" does is affect linkage. C++ functions, when compiled,
have their names mangled -- this is what makes overloading possible.
The function name gets modified based on the types and number of
parameters, so that two functions with the same name will have
different symbol names.

Code inside an extern "C" is still C++ code. There are limitations on
what you can do in an extern "C" block, but they're all about linkage.

Also, you probably want two #ifdef __cpluspluss:

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

Otherwise, your C code will never see your definitions.

Where is the best place to put the #ifdef __cplusplus extern C { #endif

There are no strict rules on this, but note the following.

  1. The general principle is that each header file takes care of itself (and is self sufficient). So, by this principle, there would be no need to wrap the header files in a extern "C", because the header files would have an extern "C" in them (if they need one). So, in the current file, you would place it after the other includes.
  2. But if you do a have a whole bunch of headers, that you don't want to add an extern "C" to, and want to make available through a single include, by all means, go ahead and wrap them up in a file wide extern "C".

Just know that the idea behind extern "C" is that it makes the compiler generate C friendly linkage. Otherwise, code compiled with a C++ compiler looks for mangled names to link against in archives compiled with a C compiler, and can't find them.

How to trigger the __cplusplus (C++) #ifdef?

"#define __cplusplus"

will let it on?

Yes, it will "let it on".

__cplusplus should be automatically defined by C++ compiler. C++ uses different name mangling and the macro often used to make C headers compatible with C++:

#ifdef __cplusplus
extern "C" {
#endif

...

#ifdef __cplusplus
}
#endif

How common is it to mix C with C++?

It is fairly common. You can either

  • compile the C stuff with C++ compiler without any issues OK, others noted that this is definitely not true: What issues can I expect compiling C code with a C++ compiler?

or

  • use both object files, static and dynamic libraries compiled with C compiler but then you should be aware of name mangling that C++ compiler does and the C compiler does not (due to the fact that C does not allow name overloading but C++ does). In such case there is extern "C" linker directive to use C linkage, i.e. does not mangle names that has been compiled with C compiler.

For good explanation how to use C linkage in C++, refer e.g. to this answer here on SO: https://stackoverflow.com/a/1041880/12118546

This is the reason you often see code like this

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Sample from: https://stackoverflow.com/a/12994075/12118546

Cryptic linker errors like these would result if you would forget about this:

...
/home/AbiSfw/ccvvuHoX.o: In function `main':
prog.cpp:(.text+0x19): undefined reference to `foo()'
...

Sample from: https://stackoverflow.com/a/12573818/12118546

calling .c file inside .cpp

Given these contents of dispmanx.c:

#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
#include <unistd.h>
#include <sys/time.h>

and main.cpp starts with

#include "dispmanx.c"

You are going to have serious problems.

You've wrapped entire standard header files such as unistd.h with extern "C", and then #include'd them in a C++ file.

They're not meant to be used that way in C++ code.

#include'ing a source file (.c, .cpp, etc) is a fundamentally bad idea in the first place. Doing it across different languages such a C and C++ and then improperly wrapping system headers with extern "C" is even worse. You can't safely compile C code as C++ code, and you certainly can't wrap system header files with extern "C" for use in C++ code.

extern "C" does not make it safe to compile C code with a C++ compiler. They are different languages, with subtle distinctions.

Why does this .c file #include itself?

The file includes itself so the same source code can be used to generate 4 different sets of functions for specific values of the macro USIZE.

The #include directives are actually enclosed in an #ifndef, which limits the recursion to a single level:

#ifndef USIZE

// common definitions
...
//

#define VSENC vsenc
#define VSDEC vsdec

#define USIZE 8
#include "vsimple.c"
#undef USIZE

#define USIZE 16
#include "vsimple.c"
#undef USIZE

#define USIZE 32
#include "vsimple.c"
#undef USIZE

#define USIZE 64
#include "vsimple.c"
#undef USIZE

#else // defined(USIZE)

// macro expanded size specific functions using token pasting

...

#define uint_t TEMPLATE3(uint, USIZE, _t)

unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {
...
}

unsigned char *TEMPLATE2(VSDEC, USIZE)(unsigned char *__restrict ip, size_t n, uint_t *__restrict op) {
...
}

#endif

The functions defined in this module are

// vsencNN: compress array with n unsigned (NN bits in[n]) values to the buffer out. Return value = end of compressed output buffer out
unsigned char *vsenc8( unsigned char *__restrict in, size_t n, unsigned char *__restrict out);
unsigned char *vsenc16(unsigned short *__restrict in, size_t n, unsigned char *__restrict out);
unsigned char *vsenc32(unsigned *__restrict in, size_t n, unsigned char *__restrict out);
unsigned char *vsenc64(uint64_t *__restrict in, size_t n, unsigned char *__restrict out);

// vsdecNN: decompress buffer into an array of n unsigned values. Return value = end of compressed input buffer in
unsigned char *vsdec8( unsigned char *__restrict in, size_t n, unsigned char *__restrict out);
unsigned char *vsdec16(unsigned char *__restrict in, size_t n, unsigned short *__restrict out);
unsigned char *vsdec32(unsigned char *__restrict in, size_t n, unsigned *__restrict out);
unsigned char *vsdec64(unsigned char *__restrict in, size_t n, uint64_t *__restrict out);

They are all expanded from the two function definitions in vsimple.c:

unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) {
...
}

unsigned char *TEMPLATE2(VSDEC, USIZE)(unsigned char *__restrict ip, size_t n, uint_t *__restrict op) {
...
}

The TEMPLATE2 and TEMPLATE3 macros are defined in conf.h as

#define TEMPLATE2_(_x_, _y_) _x_##_y_
#define TEMPLATE2(_x_, _y_) TEMPLATE2_(_x_,_y_)

#define TEMPLATE3_(_x_,_y_,_z_) _x_##_y_##_z_
#define TEMPLATE3(_x_,_y_,_z_) TEMPLATE3_(_x_, _y_, _z_)

These macros are classic preprocessor constructions to create identifiers via token pasting. TEMPLATE2 and TEMPLATE2_ are more commonly called GLUE and XGLUE.

The function template starts as:

unsigned char *TEMPLATE2(VSENC, USIZE)(uint_t *__restrict in, size_t n, unsigned char *__restrict out) ...

It is expanded in the first recursive inclusion with USIZE defined as 8 into:

unsigned char *vsenc8(uint8_t *__restrict in, size_t n, unsigned char *__restrict out) ...

The second recursive inclusion, with USIZE defined as 16, expands the template as:

unsigned char *vsenc16(uint16_t *__restrict in, size_t n, unsigned char *__restrict out) ...

and 2 more inclusions define vsenc32 and vsenc64.

This usage of preprocessed source code is more common with separate files: one for the instantiating part that has all the common definitions, especially the macros, and a separate file for the code and data templates, which is included multiple times with different macro definitions.

A good example is the generation of enums, string and structures arrays from atom and opcode definitions in QuickJS.

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

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


Related Topics



Leave a reply



Submit