In your C code, you might have seen something like the following:
This seems fine, and you might think, “Well… yes, our code is always written this way, and we’ve never encountered any issues with it.” You’re right, as long as your header files have never been referenced by any C++ programs. What does this have to do with C++? Just look at the name __cplusplus (note the two leading underscores), and you should realize it has a lot to do with C++.__cplusplus is a predefined macro specified by the C++ standard.What you can trust is:All modern C++ compilers define it; while no C language compilers do.Additionally, according to the standard, the value of __cplusplus should be 199711L, but not all compilers implement it this way; for example, the g++ compiler defines it as 1. Therefore, if the above code is referenced by a C program, its content is equivalent to the following code.
In this case, since extern “C” { } does not exist after preprocessing, the relationship between it and the #include directive is naturally non-existent.The Origin and Evolution of extern “C” In C++ compilers, there is a dark force dedicated to a task called “name mangling.” When a C++ source file is compiled, it begins to work, mangling every externally visible name it sees in the source file, and storing it in the binary object file’s symbol table. The reason such a monster exists in the C++ world is that C++ allows different definitions for a name, as long as there is no semantic ambiguity.For example, you can have two functions with the same name as long as their parameter lists differ; this is called function overloading;or you can have two functions with identical prototype declarations as long as they are in different namespaces.In fact, when in different namespaces, all names can be repeated, whether they are function names, variable names, or type names. Additionally, the construction of C++ programs still inherits the tradition of C language:the compiler treats each source code file specified by the command line as an independent compilation unit, generating object files;then, the linker links these object files together by looking up their symbol tables to generate an executable program. Compilation and linking are two separate phases;in fact, the compiler and linker are two completely independent tools. The compiler can distinguish between those symbols with the same name through semantic analysis;while the linker can only identify objects based on the names stored in the object file’s symbol table. Therefore, the purpose of name mangling by the compiler is to prevent the linker from getting confused during its operation, by re-encoding all names to generate globally unique, non-repeating new names, allowing the linker to accurately identify each name’s corresponding object. However, C language is a single namespace language and does not allow function overloading, meaning that within a compilation and linking scope, C language does not allow the existence of objects with the same name.For example, within a compilation unit, functions with the same name are not allowed, regardless of whether the function is marked with static;in all object files corresponding to an executable program, objects with the same name are not allowed, whether they represent a global variable or a function. Therefore, C language compilers do not need to perform complex processing on any names (or only perform simple consistent decoration, such as adding a single underscore _ in front of the name). Bjarne Stroustrup, the creator of C++, initially listed the ability to be compatible with C and to reuse a large number of existing C libraries as an important goal of the C++ language.However, the way the compilers of the two languages handle names is inconsistent, which brings trouble to the linking process. For example, there is a header file named my_handle.h with the following content:
Then, using a C language compiler to compile my_handle.c generates the object file my_handle.o. Since the C language compiler does not mangle names, the names of these three functions in the symbol table of my_handle.o are consistent with the declarations in the source code file.
Subsequently, we want a C++ program to call these functions, so it also includes the header file my_handle.h.Assuming the name of this C++ source file is my_handle_client.cpp, its content is as follows:
Here, the bold part is how the names of those three functions appear after mangling. Then, to make the program work, you must link my_handle.o and my_handle_client.o together.Since the naming of the same object in the two object files is different, the linker will report a related “undefined symbol” error.
To solve this problem, C++ introduces the concept of linkage specification, denoted as extern “language string”. The “language string” commonly supported by C++ compilers includes “C” and “C++”, corresponding to the C language and C++ language, respectively. The role of the linkage specification is to inform the C++ compiler that for all declarations or definitions modified by the linkage specification, they should be processed according to the specified language’s rules, such as name mangling, calling conventions, etc.There are two usages of linkage specification 1. Linkage specification for a single declaration, for example:
extern "C" void foo();
2. Linkage specification for a group of declarations, for example:
extern "C" { void foo(); int bar(); }
For our previous example, if we change the content of the header file my_handle.h to:

Then, using the C++ compiler to recompile my_handle_client.cpp, the symbol table in the generated object file my_handle_client.o will change to:

From this, we can see that the declarations modified by extern “C” now generate symbols consistent with those generated by the C language compiler. Thus, when you link my_handle.o and my_handle_client.o together again, there will no longer be the previous “undefined symbol” error.
However, at this point, if you recompile my_handle.c, the C language compiler will report a “syntax error” because extern “C” is C++ syntax, which the C language compiler does not recognize.At this point, you can use the previously discussed macro __cplusplus to distinguish between C and C++ compilers.Here is the modified code for my_handle.h:

Beware of the Unknown World Behind the Door
Now that we understand the origin and purpose of extern “C”, let’s return to our original topic: why can’t we place the #include directive inside extern “C” { … }?
Let’s look at an example. We have a.h, b.h, c.h, and foo.cpp, where foo.cpp includes c.h, c.h includes b.h, and b.h includes a.h, as follows:

Now, using the C++ compiler’s preprocessing options to compile foo.cpp, we get the following result:

As you can see, when you place the #include directive inside extern “C” { }, it causes nesting of extern “C” { }. This nesting is allowed by the C++ standard. When nesting occurs, the innermost nesting takes precedence. For example, in the following code, the function foo will use the C++ linkage specification, while the function bar will use the C linkage specification.

If you can ensure that all header files directly or indirectly dependent on a C header file are also C headers, then according to C++ language standards, this nesting should not pose any issues. However, specific implementations of certain compilers, such as MSVC2005, may report errors due to excessively deep nesting of extern “C” { }.Don’t blame Microsoft for this, as this nesting is meaningless in this context. You can completely avoid nesting by placing the #include directive outside of extern “C” { }. For instance, in the previous example, if we move all the #include directives of each header file outside of extern “C” { } and compile foo.cpp using the C++ compiler’s preprocessing options, we will get the following result:

This result will definitely not cause compilation issues—even when using MSVC.
Another significant risk of placing the #include directive inside extern “C” { } is that you may inadvertently change the linkage specification of a function declaration.For example:There are two header files a.h and b.h, where b.h includes a.h, as follows:

According to the author’s intention of a.h, the function foo is a C++ free function with a linkage specification of “C++”. However, in b.h, since #include “a.h” is placed inside extern “C” { }, the linkage specification of function foo is incorrectly changed.
Since each #include directive hides an unknown world, unless you deliberately explore it, you will never know what results or risks may arise when you place #include directives inside extern “C” { }.Perhaps you might say, “I can check these included header files; I can ensure they won’t cause any trouble.”But why bother?After all, we can completely avoid paying for unnecessary issues, can’t we?
Q&A
Q: Can any #include directive be placed inside extern “C”?
A: Just like most rules in this world, there are always exceptions.
Sometimes, you might cleverly solve some problems using the header file mechanism.For example, issues with #pragma pack.These header files serve a different purpose than regular header files; they do not contain C function declarations or variable definitions, and the linkage specification does not affect their content.In such cases, you can disregard these rules.
A more general principle is that once you understand all these principles, as long as you know what you are doing, go ahead and do it.
Q: You only mentioned what should not be placed inside extern “C”; but what can be placed inside?
A: Linkage specifications are only used to modify functions and variables, as well as function types.So, strictly speaking, you should only place these three types of objects inside extern “C”.
However, placing other C language elements, such as non-function type definitions (structures, enums, etc.) inside extern “C” will not have any impact.Not to mention macro definition preprocessor directives.
Therefore, if you value good organization and management practices, you should only use extern “C” where necessary.Even if you are somewhat lazy, in most cases, placing all definitions and declarations of a header file inside extern “C” will not cause significant issues.
Q: What if a C header file with function/variable declarations does not have an extern “C” declaration?
A: If you can determine that this header file will never be used by C++ code, then you can ignore it.
But the reality is that in most cases, you cannot accurately predict the future.Adding extern “C” now will not cost you much, but if you do not add it now, and in the future, this header file is inadvertently included by someone else’s C++ program, they will likely incur a higher cost to locate errors and fix issues.
Q: What if my C++ program wants to include a C header file a.h, which contains C function/variable declarations, but they do not use extern “C” linkage specification?
A: Add it inside a.h.
Some people might suggest that if a.h does not have extern “C”, and b.cpp includes a.h, you can add the following in b.cpp:
extern "C" { #include "a.h" }
This is an evil scheme, as we have previously explained. However, it is worth discussing that this scheme may imply an assumption that we cannot modify a.h. The reasons for not being able to modify it may come from two aspects:
-
The header file code belongs to another team or a third-party company, and you do not have permission to modify the code;
-
Even if you have permission to modify the code, since this header file belongs to a legacy system, making changes hastily may lead to unpredictable issues.
For the first case, do not attempt to work around it yourself, as it will bring you unnecessary trouble. The correct solution is to treat it as a bug and send a defect report to the relevant team or third-party company. If it is a team within your own company or a third-party company you have paid for, they are obligated to make such modifications for you. If they do not understand the importance of this matter, inform them. If these header files belong to free open-source software, make the correct modifications yourself and submit a patch to its development team.
In the second case, you need to abandon this unnecessary sense of security.Because, first of all, for most header files, such modifications are not complex or high-risk changes; everything is within a controllable range;Secondly, if a certain header file is chaotic and complex, although the philosophy for legacy systems should be:“Do not touch it until it causes trouble,” now that trouble has already arisen, it is better to face it than to avoid it, so the best strategy is to treat it as a good opportunity to organize it into a clean and reasonable state.
Q: Is our code’s usage of extern “C” as follows correct?

A: Uncertain.
According to the C++ standard definition, the value of __cplusplus should be defined as 199711L, which is a non-zero value; although some compilers do not implement it according to the standard, it can still be guaranteed that the value of __cplusplus is non-zero—at least I have not seen any compiler that implements it as 0 so far. In this case, #if __cplusplus … #endif is completely redundant.
However, there are so many C++ compiler vendors that no one can guarantee that a certain compiler, or an early version of a certain compiler, has not defined the value of __cplusplus as 0. But even so, as long as it can be ensured that the macro __cplusplus is only predefined in C++ compilers, then simply using #ifdef __cplusplus … #endif is sufficient to ensure the correctness of intent; the additional use of #if __cplusplus … #endif is, in fact, incorrect.
Only in this case: that a certain vendor’s C and C++ compilers both predefine __cplusplus, but distinguish between them by its value being 0 and non-zero, is using #if __cplusplus … #endif correct and necessary.
Since the real world is so complex, you need to clarify your goals and then define corresponding strategies based on those goals. For example: if your goal is to make your code compile with several mainstream compilers that correctly adhere to the standards, then simply using #ifdef __cplusplus … #endif is sufficient.
However, if your product is an ambitious cross-platform product that attempts to be compatible with various compilers (including unknown ones), we may have to use the following method to deal with various situations, where __ALIEN_C_LINKAGE__ is used to identify compilers that define the __cplusplus macro in both C and C++ compilations.

This should work, but writing such a long string in every header file not only looks bad but also leads to a situation where once the strategy changes, modifications will need to be made everywhere. This violates the DRY (Don’t Repeat Yourself) principle, and you will always pay an extra cost for it. A simple solution to resolve this is to define a specific header file—such as clinkage.h—where such definitions can be added:

In the following example, the C function declaration and definition are in cfun.h and cfun.c, respectively, which prints the string “this is c fun call”; the C++ function declaration and definition are in cppfun.h and cppfun.cpp, respectively, which prints the string “this is cpp fun call”. The compilation environment is vc2010.
How C++ Calls C Functions
The key to calling C functions from C++ is to ensure that C functions are compiled in the C way, not the C++ way.(1) cfun.h is as follows:
#ifndef _C_FUN_H_
#define _C_FUN_H_
void cfun();
#endif
cppfun.cpp is as follows:
//#include "cfun.h" No need to include cfun.h
#include "cppfun.h"
#include <iostream>
using namespace std;
extern "C" void cfun(); // Declared as extern void cfun(); is incorrect
void cppfun() { cout << "this is cpp fun call" << endl; }
int main() { cfun(); return 0; }</iostream>
(2) cfun.h is the same as above cppfun.cpp is as follows:
extern "C" { #include "cfun.h" // Note that the include statement must be on a separate line; }
#include "cppfun.h"
#include <iostream>
using namespace std;
void cppfun() { cout << "this is cpp fun call" << endl; }
int main() { cfun(); return 0; }</iostream>
(3) cfun.h is as follows:
#ifndef _C_FUN_H_
#define _C_FUN_H_
#ifdef __cplusplus
extern "C" {
#endif
void cfun();
#ifdef __cplusplus
}
#endif
#endif
cppfun.cpp is as follows:
#include "cfun.h"
#include "cppfun.h"
#include <iostream>
using namespace std;
void cppfun() { cout << "this is cpp fun call" << endl; }
int main() { cfun(); return 0; }</iostream>
How C Calls C++ Functions
The key to calling C++ from C is that C++ provides a function that conforms to C calling conventions.
When tested on vs2010, without declaring extern or anything, simply including cppfun.h in cfun.c and calling cppfun() can compile and run; however, it will compile with errors under gcc, as this approach should be incorrect according to C++/C standards.Both of the following methods can run on both compilers.
cppfun.h is as follows:
#ifndef _CPP_FUN_H_
#define _CPP_FUN_H_
extern "C" void cppfun();
#endif
cfun.c is as follows:
//#include "cppfun.h" // Do not include the header file, otherwise it will compile with errors
#include "cfun.h"
#include <stdio.h>
void cfun() { printf("this is c fun call\n"); }
extern void cppfun();
int main() { #ifdef __cplusplus cfun(); #endif cppfun(); return 0; }</stdio.h>