Cippi mixes C with C++ code.
Due to the shared history of C and C++, both languages are closely related. Because neither of them is a subset of the other, you have to know a few rules to mix them.
This section in the C++ Core Guidelines consists of three rules. These three rules cover the typical issues encountered when dealing with legacy code.
Without further ado, here is the reason the C++ Core Guidelines prefer C++: “C++ provides better type checking and more notational support [than C]. It provides better support for high-level programming and often generates faster code.”
If you must use C, use the common subset of C and C++, and compile the C code as C++ |
The first question that you have to answer when mixing C and C++ is, Can you compile the entire code base with a C++ compiler?
If the entire source code is available, you are almost done. I say “almost” because C is not a subset of C++. Here is a small and bad C program that breaks with a C++ compiler.
// cStyle.c #include <stdio.h> int main() { double sq2 = sqrt(2); // (1) printf("\nsizeof(\'a\'): %d\n\n", sizeof('a')); // (2) char c; void* pv = &c; int* pi = pv; // (3) int class = 5; // (4) }
First, let me compile the program and execute it with the C90 standard. The compilation succeeds with a few warnings (see Figure 14.1).
Figure 14.1 Warnings with a C compiler
The program cStyle.c
has a few issues. There is no declaration for the sqrt
function (1), (3) performs an implicit conversion from a void
pointer to an int
pointer, and (4) uses the class
keyword.
Let’s see how the C++ compiler reacts to the same code. See Figure 14.2.
Figure 14.2 Errors with a C++ compiler
I get what I deserve: three compiler errors. The program cStyle.c
shows more subtle differences between a C and a C++ compiler. Figure 14.3 shows the program reduced (2): printf("\nsizeof(\'a\'): %d\n\n", sizeof('a'));
. Here is the output.
Figure 14.3 Different size of a char with a C++ compiler
Instead of 4, such as for the C compiler, sizeof('a')
is 1 with the C++ compiler. 'a'
is an int
in C.
Now, let’s discuss the more challenging case where the entire source code is not available.
These are the essential points.
Use your C++ compiler to compile your main
function. In contrast to a C compiler, a C++ compiler generates additional startup code that is executed before the main
function. For example, the startup code calls constructors of global (static) objects.
Use your C++ compiler to link your program. The C++ compiler, when used for linking the program, automatically links in standard C++ libraries.
Use a C and C++ compiler from the same vendor, which should have the same calling conventions. A calling convention specifies the method that a compiler sets up to access a function. This includes in which order parameters are allocated, how parameters are passed, or whether the caller or the callee prepares the stack. Read the full details of x86’s calling conventions on Wikipedia (https://en.wikipedia.org/wiki/X86_calling_conventions).
If you must use C for interfaces, use C++ in the calling code using such interfaces |
In contrast to C, C++ supports function overloading. This means that you can define functions having the same name but different parameters. The compiler picks the right function when a function is invoked.
// functionOverloading.cpp #include <iostream> void print(int) { std::cout << "int" << '\n'; } void print(double) { std::cout << "double" << '\n'; } void print(const char*) { std::cout << "const char* " << '\n'; } void print(int, double, const char*) { std::cout << "int, double, const char* " << '\n'; } int main() { std::cout << '\n'; print(10); print(10.10); print("ten"); print(10, 10.10, "ten"); std::cout << '\n'; }
The output is as expected (see Figure 14.4).
Figure 14.4 Function overloading
The interesting question is now: How can the C++ compiler distinguish the various functions? The C++ compiler additionally encodes the type and numbers of the parameters into the function name. This process is called “name mangling” and is specific for each C++ compiler. The process, which is not standardized, is often also called “name decoration.”
With the help of the functionOverloading.cpp
on Compiler Explorer, it is quite easy to see the mangled names. Just disable the button Demangle.
Table 14.1 shows the names that the GCC 8.3 and the MSVC 19.16 are producing.
Table 14.1 Name mangling
Function |
GCC 8.3 |
MSVC 19.16 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
By using the extern "C"
linkage specifier, you can prevent the C++ compiler from mangling the names. The result is that you can call a C function from C++ or a C++ function from C.
You can use extern "C"
for
Each function.
extern "C" void foo(int);
Each function in a scope.
extern "C" { void foo(int); double bar(double); }
The entire header file by using include guards. The macro __cplusplus
is defined when the C++ compiler is used.
#ifdef __cplusplus extern "C" { #endif void foo(int); double bar(double); . . . #ifdef __cplusplus } #endif