Instant C++ for C programmers
So, your boss gave you the job of moving all your existing C code to the new C++ compiler… and you have no idea of where to begin or where the problems might be.
Since there are many reasons to move from C to C++—some good and some bad—we will not go into any discussions about that. Instead, this article focuses on getting your dusty old C application up and running on your shiny new C++ compiler. To begin with, we will not consider all the fancy stuff in C++, such as classes, constructors, or destructors; instead we will concentrate on compiling your C application using a C++ compiler.
For the most part, making a C application compile under a C++ compiler is not that hard—because most of C is actually valid as C++ code—but there are several dangerous differences that could give you a severe headache. The easy ones are those that the compiler complains about. It is the ones that compile just fine, but do completely different things that are tricky.
Declarations
Declarations in C++ (both function and type declarations) behave differently from those in C. In C++, the name of a class, structure, union, and enumeration is automatically introduced as a type name. Compare the following two pieces of C and C++ code:
C code
|
C++ code
|
|
|
Observe that the typedef lines are removed from the C++ version because they are not needed. Keeping them when moving from C to C++ is not a problem: they will compile just fine.
Automatically introducing the name of, for example, a structure as a type name is quite convenient, most of the time, but can cause some subtle errors. For example, the following C code will (probably) crash hard when compiled using a C++ compiler:
char tone[100];
void dial() {
struct tone {
short number;
char freq[3];
};
...
char* tone_data = (char*) malloc(sizeof(tone));
}
Here, sizeof(tone) will be 100 when compiling with a C compiler and (at least) 5 when compiling with a C++ compiler, since tone refers to the array in C but to the structure in C++. (In general, you should not give the same name to a type and a variable or field; but that is another story.)
In C, functions that are declared without giving explicit argument types accept any number of arguments; in C++ it declares a function taking no arguments. Also, in C it is permitted (but considered poor style so you will get a warning about it) to use a function that has not previously been declared; this is not allowed in C++.
void f();
int main() {
f(3); /* Error: too many arguments in function call */
g(19); /* Error: identifier "g" is undefined */
}
A proper use of function prototypes will take care of all these problems. C is also very forgiving about omitting the types of variables and return types of functions (even if this is also considered poor style), where it implicitly uses int. Also here, C++ is very unforgiving and requires an explicit declaration of the type. For example, the following code compiles fine as C, but does not pass as proper C++:
foo() { /* Warning: omission of explicit
const v = 12; type is nonstandard */
... /* Warning: omission of explicit
} type is nonstandard */
Pointer conversion
The C language is quite relaxed when it comes to implicit conversions between many different types, but here C++ is very picky. In contrast to C, C++ has strong type checking. The typical case you will run into is when you try to compile the following small piece of code:
#include <stdlib.h>
int main() {
int* px = malloc(sizeof(int));
/* Error: a value of type "void *" cannot be used to initialize an entity of type "int *" */
}
Except for the fact that you should prefer using new to malloc in C++ programs, the above code is not valid C++. The reason is that implicit conversions from the void* type to any other pointer type (such as int*) is not allowed in C++. To get the above code to compile on a C++ compiler, you have to use an explicit cast to get an int* from the void*.
Enumerations
There is a similar situation for enumeration constants: in C it is permitted to implicitly convert from an int to an enumeration type, but in C++ this is forbidden. Therefore, the following code is legal C code (although you will get a friendly warning), but invalid as C++ code:
enum fruit { apple, orange, banana };
typedef enum fruit fruit_t;
void print(fruit_t i) {
int* px = malloc(sizeof(int));
switch(i) {
case apple:
printf("apple"); break;
case orange:
printf("orange"); break;
case banana:
printf("banana"); break;
}
}
int main() {
int i;
for (i = 0 ; i < 3 ; ++i) {
print(i);
/* Error: argument of type "int" is incompatible with parameter of type "fruit_t" */
}
}
Instead you have to write the following code:
print((fruit_t) i);
Guidelines
The following guidelines will help you move your C application to a C++ compiler:
- Always use a naming convention that differentiates between names of types and names of fields and variables. For example, use fruit_t, tone_t, range_t as type names and use fruit, tone, and range as names of fields or variables.
- Always use explicit casts when converting from a more general type (such as void* or int) to a more restrictive type (such as, int* or enum).
- Declare prototypes for all functions that you use.
- Be explicit about what types you are using: do not trust implicitly introduced types for return values or variables. If you are experienced in programming embedded systems this will probably not be an issue, but there are many subtle cases.