Программирование ARM Быстрый переход с языка C на C++ Thu, April 25 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Быстрый переход с языка C на C++ Печать
Добавил(а) microsin   

Итак, начальник дал Вам срочное задание перенести весь имеющийся код 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):

1
print((fruit_t) i);

[Рекомендации по переводу кода 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 (оригинал статьи на английском языке).

 

Добавить комментарий


Защитный код
Обновить

Top of Page