This page looks best with JavaScript enabled

[MECpp]Item-34 Understand How to Combine C++ and C in the Same Program

 ·  ☕ 5 min read · 👀... views

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 from new; always use free with memory from malloc
  • 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 ints and doubles, 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++:

1
2
3
void drawLine(int x1, int y1, int x2, int y2); // suppose this is mangled into xyzzy
...
drawLine(a, b, c, d);  // call to unmangled function name in source code

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:

1
xyzzy(a, b, c, d);  // call to mangled function name

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:

1
2
extern "C"
void drawLine(int x1, int y1, int x2, int y2);

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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#ifdef __cplusplus
extern "C" {
#endif
    void drawLine(int x1, int y1, int x2, int y2);
    void twiddleBits(unsigned char bits);
    void simulate(int iterations);
    ...
#ifdef __cplusplus
}
#endif

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):

1
2
3
4
5
6
7
8
int main(int argc, char *argv[])
{
    performStaticInitialization(); // generated by the compiler

    the statements we put in main;

    performStaticDestruction(); // generated by the compiler
}

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++:

1
2
3
4
5
6
7
extern "C" 
int realMain(int argc, char *argv[]); // implement this function in C

int main(int argc, char *argv[]) // write this in C++
{
    return realMain(argc, argv);
}

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 newed pointer, nor deleteing a malloced 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:

1
char * strdup(const char *ps); // return a copy of the string pointed to by ps

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, structs and variables of built-in types (e.g., ints, chars, 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.

Share on
Support the author with