Итак, начальник дал Вам срочное задание перенести весь имеющийся код C на новый компилятор C++... и у Вас нет идей с чего начать, и где могут вылезти проблемы при таком портировании [1].
Поскольку есть много причин перевода кода C на C++ (некоторые причины действительно логичные и правильные, а некоторые совсем нет) мы не будем долго рассуждать о них. Вместо этого статья будет сфокусирована на том, как поднять Вашу старую пыльную программу C на высоту нового сияющего компилятора C++. Для того, чтобы начать, мы также не будем рассматривать все занятные бантики и рюшечки C++ наподобие классов, конструкторов или деструкторов; сконцентрируемся лучше на компиляции Вашего кода C, используя компилятор C++.
По большей части скомпилировать приложение C компилятором C++ не составит большого труда, потому что почти все особенности C в действительности допустимы и в коде C++, но есть несколько опасных различий, которые могут доставить Вам головную боль. Самые легкие из них те, на которые жалуется компилятор. Есть и такие, которые компилируются отлично и без нареканий, но делают совершенно разные вещи, и причину этого не всегда просто понять.
[Декларации (Declarations)]
Декларации в C++ (и декларации функции, и декларации типа) ведут себя по-другому, не как в C. На C++ имя класса, структуры, объединения (union) и перечисления (enumeration) автоматически представляет имя типа. Сравните следующие два варианта кода C и C++:
Код C |
Код C++ |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
enum fruit
{
apple, orange, banana
};
typedef enum fruit fruit;
struct person
{
char name[20];
int age;
fruit favorite;
};
typedef struct person person;
void foo()
{
person thomas;
}
|
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
enum fruit
{
apple, orange, banana
};
struct person
{
char name[20];
int age;
fruit favorite;
};
void foo()
{
person thomas;
}
|
|
Обратите внимание, что строки typedef были удалены из версии C++ кода, потому что они больше не нужны. Если их все же оставить при переносе кода C на C++, то это не создаст проблемы: все скомпилируется нормально.
Автоматический ввод имени, к примеру, структуры как имени типа чаще всего довольно удобно, но это может вызвать некоторые тонкие и неочевидные ошибки. Например, следующий код C будет (вероятно) рушиться, когда будет компилироваться компилятором C++:
1
2
3
4
5
6
7
8
9
10
11
12
|
char tone[100];
void dial()
{
struct tone
{
short number;
char freq[3];
};
...
char* tone_data = (char*) malloc(sizeof(tone));
}
|
Здесь sizeof(tone) будет 100, когда компилирование идет компилятором C и, (как минимум) 5, когда компилирование производится компилятором C++, поскольку tone дает ссылку на массив в случае языка C, но в случае C++ это будет ссылка на структуру (вообще Вы не должны давать одинаковое имя типу и переменной или полю; но это совсем другая история).
В языке C функции, которые декларированы без явного указания типов аргумента могут принимать любое количество аргументов; в языке C++ это декларирует функцию, у которой нет аргументов. Также на C разрешено (но считается плохим стилем, на которое будут выдаваться варнинги) использовать функцию, которая не была перед этим задекларирована; на C++ это не разрешено.
1
2
3
4
5
6
7
8
9
|
void f();
int main()
{
f(3); /* Ошибка: слишком много аргументов в вызове функции
(too many arguments in function call). */
g(19); /* Ошибка: идентификатор "g" не определен
(identifier "g" is undefined). */
}
|
Надлежащее использование прототипов функции позаботится обо всех этих проблемах. C также весьма прощает опускание типов переменных и типов возвращаемых из функций значений (несмотря на то, что это также считается плохим стилем), где он неявно использует int. Здесь C++ это не прощает, и требует явного декларирования типа. Например, следующий код нормально компилируется как C, но не проходит компиляцию как C++:
1
2
3
4
5
6
7
|
foo() /* Предупреждение: нестандартный пропуск явного указания типа
(Warning: omission of explicit type is nonstandard). */
{
const v = 12; /* Предупреждение: нестандартный пропуск явного указания типа
(Warning: omission of explicit type is nonstandard). */
...
}
|
[Преобразование указателя (Pointer conversion)]
Язык C весьма спокойно относится к неявным преобразованиям между разными типами (implicit type cast), но C++ здесь очень придирчив. В отличие от C, язык C++ использует строгую проверку типа. Типичный случай, когда Вы столкнетесь с этим, будет при компиляции такого маленького кусочка кода:
1
2
3
4
5
6
7
8
9
|
#include < stdlib.h >
int main()
{
int* px = malloc(sizeof(int));
/* Ошибка: переменная типа "void *" не может быть использована для инициализации
переменной типа "int *" (a value of type "void *" cannot be used to initialize
an entity of type "int *"). */
}
|
За исключением факта, что Вы должны использовать new для malloc в программах C++, вышеуказанный код не будет допустимым кодом C++. Причина в том, что неявные преобразования от типа void* к любому другому типу указателя (такому как int*) запрещены в C++. Чтобы этот код компилировался под управлением компилятора C++, используйте явное преобразование типа (explicit type cast), чтобы получить int* из void*.
[Перечисления (Enumerations)]
Есть подобная ситуация с константами перечисления (enumeration constants): в C разрешено неявно преобразовать данные из int в перечисляемый тип, но в C++ это запрещено. Таким образом, следующий код для C будет совершенно легальным (хотя Вы получите дружеское предупреждение), но является недопустимым кодом для C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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;
break;
case orange:
printf("orange"); break;
break;
case banana:
printf("banana"); break;
break;
}
}
int main()
{
int i;
for (i = 0 ; i < 3 ; ++i)
{
print(i); /* Ошибка: аргумент типа "int" несовместим с типом
параметра "fruit_t" (argument of type "int" is
incompatible with parameter of type "fruit_t"). */
}
}
|
Чтобы исправить ошибку, используйте явное приведение (преобразование) типа (explicit cast):
[Рекомендации по переводу кода C на C++]
Следующие простые рекомендации помогут портировать Ваш код C на компилятор C++:
1. Всегда используйте стиль именования (naming convention), который делает отличие имен типов от имен полей структур и переменных. Например, используйте fruit_t, tone_t, range_t как имена типа, и используйте fruit, tone и range как имена полей или переменных. 2. Всегда используйте явное преобразование типа (explicit cast), когда делаете конвертацию от одного из основных типов (таких как void* или int) к более ограниченному типу (такому как int* или enum). 3. Декларируйте прототипы для всех функций, которые используете. 4. Четко представляйте типы, которые используете: не доверяйте неявно заданным типам для возвращаемых значений или переменных. Если у Вас есть опыт в программировании встраиваемых систем (embedded systems), то это вероятно не создаст проблемы, но есть много тонких случаев.
[Ссылки]
1. Instant C++ for C programmers site:iar.com (оригинал статьи на английском языке). |