There are five points worth noting if we want to mix C++ and C in the same program.
Summary
- Make sure the C++ and C compilers produce compatible object files
- Declare functions to be used by both languages
extern C
- If at all possible, write
main
in C++ - Always use
delete
with memory fromnew
; always usefree
with memory frommalloc
- Limit what we pass between the two languages to data structures that compile under C; the C++ version of structs may contain nonvirtual member functions
1. Compatible object files
Before mix together object files produced by some C compiler with those from C++ compiler, we have to make sure they both share the same implementation-dependent features, such as the size of int
s and double
s, the mechanism by which parameters are passed from caller to callee, and whether the caller or the callee orchestrates the passing.
2. Name Mangling
Name mangling is the process through which the C++ compilers give each function in our program a unique name, which is unnecessary in C because we can’t overload function names in C.
For example, when we write this in C++:
|
|
Then the statement will be translated by the C++ compiler into the mangled version of that function, so the object file conbtains a function call that corresponds to this:
|
|
However, if drawLine
is a C function, the object file (or archive or dynamically linked library, etc.) that contains the compiled version of drawLine
contains a function called drawLine
- no name mangling occurs. When trying to link mixed style object files together, we may get an error, because the linker is looking for a function called xyzzy
, and there is no such function.
To solve this problem, we tell C++ compilers not to mangle certain function names:
|
|
Note that there is only extern "C"
, no extern "Pascal"
or extern "FORTRAN"
or anything else. extern "C"
means that the function should be called as if it were written in C (Technically, extern "C"
means the function has C linkage, which guarantees that name mangling is suppressed.)
For a slew of functions whose names don’t need mangling, we enclose them in curly braces. For header files we want to share by both C++ and C, we take advantage of preprocessor symbol “__cplusplus”, which is defined only for C++ compilations:
|
|
3. Initialization of Statics
In C++, the constructors of static class objects and objects at global, namespace, and file scope are usually called before the body of main
is executed, which is known as static initialization. Similarly, objects that are created through static initialization must have their destructors called during static destruction, which typically take place after main
has finished executing.
The implementation of static initialization as well as static destruction is usually achieved by compilers by inserting a call to a special compiler-written function at the beginning of main
, which takes care of static initialization (similarly with static destruction):
|
|
The point is, if a software contains a C++ part, which is compiled with this approach to initialize and destruct static objects (they usually do), we should write main
in C++.
If most of a program is in C and C++ is only a support library, it would make more sense to write main
in C. Nevertheless, in case of static objects in C++ library (if it doesn’t now, it probably will in the future, see MECpp item 32), so it’s still a good idea to write main
in C++: simply call the C version realMain
in a wrapper main
in C++:
|
|
4. Dynamic Memory Allocation
Recall MECpp Item 8: the C++ parts of a program use new
and delete
, and the C parts of a program use malloc
(and its variants) and free
. Mismatched allocation and deallocation operation for dynamic memory yields undefined behavior, so never call free
on a new
ed pointer, nor delete
ing a malloc
ed pointer.
Sometimes this is easier said than done, because some functions are not in the standard library, or not available in the uniform implementation on different computing platforms, making it hard to judge the correct deallocation operation:
|
|
If the strdup
is from a C library, we need to call free
; if it was written for a C++ library, we should call delete
. If we can’t make sure, then simply avoid calling such functions.
5. Data Structure Compatibility
C functions can not understand C++ features, so if we want to pass data between C++ and C programs, we are limited to those concepts that C can express: naturally, struct
s and variables of built-in types (e.g., int
s, char
s, etc.)
Because the rules governing the layout of a struct
in C++ are consistent with those of C, if we can add structs with nonvirtual member, objects of such structs (or class) containing only non-virtual functions should be compatible with their C counterparts, whose structure definition lacks only the member function declarations, and we are safe to pass them back and forth between C++ and C.
Adding virtual functions ends the game, because the addition of virtual functions to a class cuases objects of that type to use a different memory layout (MECpp item 24). Having a struct inherit from another struct (or class) usually changes its layout, too, so structs with base structs (or classes) are also poor candidates for exchange with C functions.