C++ – Combining C++ and C – how does #ifdef __cplusplus work


I'm working on a project that has a lot of legacy C code. We've started writing in C++, with the intent to eventually convert the legacy code, as well. I'm a little confused about how the C and C++ interact. I understand that by wrapping the C code with extern "C" the C++ compiler will not mangle the C code's names, but I'm not entirely sure how to implement this.

So, at the top of each C header file (after the include guards), we have

#ifdef __cplusplus
extern "C" {

and at the bottom, we write

#ifdef __cplusplus

In between the two, we have all of our includes, typedefs, and function prototypes. I have a few questions, to see if I'm understanding this correctly:

  1. If I have a C++ file A.hh which
    includes a C header file B.h,
    includes another C header file C.h,
    how does this work? I think that
    when the compiler steps into B.h,
    __cplusplus will be defined, so it
    will wrap the code with extern "C"
    (and __cplusplus will not be
    defined inside this block). So,
    when it steps into C.h,
    __cplusplus will not be defined
    and the code will not be wrapped in
    extern "C". Is this correct?

  2. Is there anything wrong with
    wrapping a piece of code with
    extern "C" { extern "C" { .. } }?
    What will the second extern "C"

  3. We don't put this wrapper around the .c files, just the .h files. So, what happens if a function doesn't have a prototype? Does the compiler think that it's a C++ function?

  4. We are also using some third-party
    code which is written in C, and does
    not have this sort of wrapper around
    it. Any time I include a header
    from that library, I've been putting
    an extern "C" around the #include.
    Is this the right way to deal with

  5. Finally, is this set up a good idea?
    Is there anything else we should do?
    We're going to be mixing C and C++
    for the foreseeable future, and I
    want to make sure we're covering all
    our bases.

Best Solution

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.