Несовместимость Си и C++Несовместимость Си и C++ — особенности языков программирования C++ и Си, затрудняющие перенос кода на языке Си на C++. Несмотря на то, что C++ создавался как потомок достандартизированного Си, и по большей части был совместим с ним на тот момент на уровне исходного кода и компоновки[1][2], Си не является подмножеством C++[3], поэтому нетривиальные программы на Си не будут компилироваться на C++ без изменений. При этом средства разработки для обоих языков (такие, как среды разработки и компиляторы) часто интегрируются в один продукт, при этом программист может выбрать Си или C++ в качестве языка исходного кода. Также C++ вводит множество возможностей, недоступных в Си, и на практике почти весь код, написанный на C++, не соответствует коду на Си. Наибольшей сложностью является то, что соответствующий си-код оказывается неправильно написанным (англ. ill-formed) кодом на C++ либо, даже будучи корректным на обоих языках, может вести себя по-разному на Си и C++. Создатель C++ Бьёрн Страуструп предлагал[4] бороться с несовместимостью языков. Ряд других авторов утверждает, что, поскольку Си и C++ — это два разных языка, совместимость между ними полезна, но не жизненно важна; согласно их мнению, усилия по уменьшению несовместимости не должны препятствовать попыткам улучшить каждый язык в отдельности. Третьи утверждают, что почти каждая синтаксическая ошибка, которую можно допустить в Си, была пересмотрена в C++ таким образом, чтобы порождать компилируемый, хоть не обязательно корректный код[5]. Официальное обоснование стандарта C 1999 года (C99) «поддерживает принцип сохранения наибольшего общего подмножества» между C и C++, «сохраняет при этом различия между ними и позволяет развиваться отдельно», там также утверждается, что авторы были «довольны тем, что C++ стал большим и амбициозным языком»[6]. Некоторые нововведения стандарта C99 не поддерживаются в стандарте C++ или конфликтуют с отдельными возможностями C++, например, массивы переменной длины, собственные комплексные типы данных и квалификатор типа Конструкции, допустимые в Си, но не в C++C++ применяет более строгие правила типизации (никаких неявных нарушений системы статических типов[1]) и требования к инициализации (принудительная проверка во время компиляции, что у переменных в области видимости не нарушена инициализация, то есть невозможно вернуться к месту до объявления с явной или неявной инициализацией, если не считать блоки, в которые не управляющий поток не попадал)[8], и поэтому некоторый допустимый код Си недопустим в C++. Обоснование этого приведено в Приложении C.1 к стандарту ISO C++[9]. Одно из часто встречающихся отличий заключается в том, что Си более слабо типизирован в отношении указателей. В частности, Си позволяет присваивать указатель void *ptr;
/* Неявное преобразование из void* в int* */
int *i = ptr;
или аналогично: int *j = malloc(5 * sizeof *j); /* Неявное преобразование из void* в int* */
Чтобы заставить код компилироваться как на Си, так и на C++, необходимо использовать явное приведение типа следующим образом (с некоторыми предостережениями в отношении обоих языков[11][12]): void *ptr;
int *i = (int *)ptr;
int *j = (int *)malloc(5 * sizeof *j);
C++ имеет более сложные правила присваивания указателей, которые добавляют квалификаторы, поскольку C++ позволяет приводить C++ изменяет некоторые функции стандартной библиотеки языка Си, добавляя дополнительные перегруженные функции с квалификатором типа C++ также более строг в преобразованиях в перечисления: целые числа не могут быть неявно преобразованы в перечисления, как в Си. Кроме того, константные перечисления ( В C++ Компиляторы C++ запрещают void fn(void)
{
goto flack;
int i = 1;
flack:
;
}
Несмотря на синтаксическую корректность, функция Си допускает несколько предварительных определений одной глобальной переменной в одной единице трансляции, что недопустимо в C++, так как это нарушение правила одного определения (англ. One Definition Rule, ODR). int N;
int N = 10;
В Си допустимо объявление нового типа с тем же именем, что и у enum BOOL {FALSE, TRUE};
typedef int BOOL;
Объявления функций без прототипов (в стиле Кернигана — Ритчи) недопустимы в C++; они по-прежнему действительны в Си[14], хотя были признаны устаревшими с момента первой стандартизации Си в 1990 году. «Устаревший» (англ. obsolescent) — это термин, которому даётся определение в стандарте ISO C, он означает языковую возможность, которая «может быть удалена в будущих версиях» стандарта. Аналогично, неявные объявления функций (использование функций, которые не были объявлены) не допускаются в C++ и являются недопустимыми в Си с 1999 года. В Си прототип функции без аргументов, например Как на Си, так и на C++ можно определить вложенные типы Си позволяет объявлять типы C99 и C11 добавили в Си несколько дополнительных возможностей, которые не были включены в стандартный C++, таких как комплексные числа, массивы переменной длины (при этом комплексные числа и массивы переменной длины обозначены как необязательные расширения в C11), гибкий элемент массива[англ.], ключевое слово restrict, квалификаторы параметров массива, составные литералы (англ. compound literals) и назначенные инициализаторы[англ.]. Комплексная арифметика с использованием примитивных типов данных Массивы переменной длины в C99 приводят к возможному вызову оператора void foo(size_t x, int a[*]); // Объявление VLA
void foo(size_t x, int a[x])
{
printf("%zu\n", sizeof a); // То же, что и sizeof(int*)
char s[x * 2];
printf("%zu\n", sizeof s); // Будет выведено print x*2
}
Последний элемент структурного типа в стандарте C99 с более чем одним элементом может быть гибким элементом массива[англ.], который принимает синтаксическую форму массива с неопределённой длиной. Это служит цели, аналогичной массивам переменной длины, но массивы переменной длины не могут отображаться в определениях типов, и, в отличие от массивов переменной длины, элементы гибкого массива не имеют определенного размера. ISO C++ не имеет такой особенности. Например: struct X
{
int n, m;
char bytes[];
}
Квалификатор типа Квалификаторы параметров массива в функциях поддерживаются в Си, но не в C++: int foo(int a[const]); // аналогично int *const a
int bar(char s[static 5]); // отмечает, что s имеет длину не менее 5 символов
Функциональность составных литералов в Си обобщается как на встроенные, так и на пользовательские типы с помощью синтаксиса списочной инициализаци в C++11, хотя и с некоторыми синтаксическими и семантическими различиями: struct X a = (struct X){4, 6}; // Аналогичным в C++ было бы X{4, 6}. Синтаксическая форма C, используемая в C99, поддерживается в качестве расширения в компиляторах GCC и Clang.
foo(&(struct X){4, 6}); // Объект выделяется на стеке, и его адрес может быть передан функции. Не поддерживается в C++.
if (memcmp(d, (int []){8, 6, 7, 5, 3, 0, 9}, n) == 0) {} // Аналогичным в C++ было бы digits = int []; if (memcmp(d, digits{8, 6, 7, 5, 3, 0, 9}, n) == 0) {}
Назначенные инициализаторы для массивов допустимы только в Си: char s[20] = { [0] = 'a', [8] = 'g' }; // Допустимо в C, но не в C++
Функции, которые не возвращают значения, могут быть отмечены с помощью атрибута noreturn в C++, тогда как C использует другое ключевое слово. C++ добавляет множество дополнительных ключевых слов для поддержки своих новых возможностей. Это делает код на C, использующий эти ключевые слова для идентификаторов, недопустимым в C++. Например, такой код: struct template
{
int new;
struct template* class;
};
является допустимым кодом на Си, но отклоняется компилятором C++, поскольку ключевые слова Конструкции, которые ведут себя по-разному в Си и C++Существует несколько синтаксических конструкций, которые допустимы как в Си, так и в C++, но дают разные результаты в этих языках. Символьные литералы[англ.], такие как C++ использует внутреннюю компоновку В Си использование встроенных функций требует, чтобы объявление прототипа функции с использованием ключевого слова И C99, и C++ имеют логический тип Некоторые другие различия из предыдущего раздела также могут быть использованы для создания кода, который компилируется на обоих языках, но ведёт себя по-разному. Например, следующая функция будет возвращать разные значения в C и C++: extern int T;
int size(void)
{
struct T { int i; int j; };
return sizeof(T);
/* C: вернёт sizeof(int)
* C++: вернёт sizeof(struct T)
*/
}
Это связано с тем, что Си требует наличия Связывание кода Си и C++В то время как Си и C++ поддерживают высокую степень совместимости исходных текстов, объектные файлы, создаваемые их компиляторами, могут иметь важные различия, которые проявляются при смешивании кода Си и C++. Компиляторы Си не выполняют name mangling[англ.] символов, как это делают компиляторы C++[18]. В зависимости от компилятора и архитектуры соглашения о вызовах могут различаться между языками. Чтобы код на C++ вызывал функцию на Си Обычная практика в заголовочных файлах для поддержания совместимости как с Си, так и C++ — добавлять в них объявление с /* Заголовочный файл foo.h */
# ifdef __cplusplus /* Если это компилятор C++, использовать компоновку, как в C */
extern "C" {
# endif
/* У этих функций компоновка, как в языке C */
void foo();
struct bar { /* ... */ };
# ifdef __cplusplus /* Если это компилятор C++, завершите использование компоновки, как в C */
}
# endif
Различия между соглашениями о компоновке и вызовах Си и C++ также могут иметь некие последствия для кода, использующего указатели на функции. Некоторые компиляторы дадут нерабочий код, если указатель на функцию, объявленный как Например, следующий код: void my_function();
extern "C" void foo(void (*fn_ptr)(void));
void bar()
{
foo(my_function);
}
Компилятор C++ от Sun Microsystems выдаёт следующее предупреждение: $ CC -c test.cc
"test.cc", line 6: Warning (Anachronism): Formal argument fn_ptr of type
extern "C" void(*)() in call to foo(extern "C" void(*)()) is being passed
void(*)().
Это связано с тем, что Примечания
Ссылки
|
Portal di Ensiklopedia Dunia