Несовместимость Си и C++ — особенности языков программирования C++ и Си, затрудняющие перенос кода на языке Си на C++.
Несмотря на то, что C++ создавался как потомок достандартизированного Си, и по большей части был совместим с ним на тот момент на уровне исходного кода и компоновки, Си не является подмножеством C++, поэтому нетривиальные программы на Си не будут компилироваться на C++ без изменений. При этом средства разработки для обоих языков (такие, как среды разработки и компиляторы) часто интегрируются в один продукт, при этом программист может выбрать Си или C++ в качестве языка исходного кода.
Также C++ вводит множество возможностей, недоступных в Си, и на практике почти весь код, написанный на C++, не соответствует коду на Си.
Наибольшей сложностью является то, что соответствующий си-код оказывается неправильно написанным () кодом на C++ либо, даже будучи корректным на обоих языках, может вести себя по-разному на Си и C++.
Создатель C++ Бьёрн Страуструп предлагал бороться с несовместимостью языков. Ряд других авторов утверждает, что, поскольку Си и C++ — это два разных языка, совместимость между ними полезна, но не жизненно важна; согласно их мнению, усилия по уменьшению несовместимости не должны препятствовать попыткам улучшить каждый язык в отдельности. Третьи утверждают, что почти каждая синтаксическая ошибка, которую можно допустить в Си, была пересмотрена в C++ таким образом, чтобы порождать компилируемый, хоть не обязательно корректный кодсм. The UNIX-HATERS Handbook, с.208. Официальное обоснование стандарта C 1999 года (C99) «поддерживает принцип сохранения наибольшего общего подмножества» между C и C++, «сохраняет при этом различия между ними и позволяет развиваться отдельно», там также утверждается, что авторы были «довольны тем, что C++ стал большим и амбициозным языком»Rationale for International Standard—Programming Languages—C , revision 5.10 (April 2003)..
Некоторые нововведения стандарта C99 не поддерживаются в стандарте C++ или конфликтуют с отдельными возможностями C++, например, массивы переменной длины, собственные комплексные типы данных и квалификатор типа restrict. С другой стороны, C99 уменьшил некоторые другие несовместимости по сравнению с C89, включив такие функции C++, как однострочные комментарии //, а также смешение объявлений и кода.
Конструкции, допустимые в Си, но не в C++ C++ применяет более строгие правила типизации (никаких неявных нарушений системы статических типов) и требования к инициализации (принудительная проверка во время компиляции, что у переменных в области видимости не нарушена инициализация, то есть невозможно вернуться к месту до объявления с явной или неявной инициализацией, если не считать блоки, в которые не управляющий поток не попадал) («It is invalid to jump past a declaration with explicit or implicit initializer (except across entire block not entered). … With this simple compile-time rule, C++ assures that if an initialized variable is in scope, then it has assuredly been initialized.»), и поэтому некоторый допустимый код Си недопустим в C++. Обоснование этого приведено в Приложении C.1 к стандарту ISO C++.
Одно из часто встречающихся отличий заключается в том, что Си более слабо типизирован в отношении указателей. В частности, Си позволяет присваивать указатель void* любому типу указателя без приведения, в то время как C++ этого не позволяет; эта идиома часто встречается в коде на Си, использующем для выделения памяти malloc, или при передаче контекстных указателей в pthreads (POSIX API) и другие фреймворки, использующие обратные вызовы. Например, следующее допустимо в Си, но не в C++:
void *ptr; /* Неявное преобразование из void* в int* */ int *i = ptr;
или аналогично:
int *j = malloc(5 * sizeof *j); /* Неявное преобразование из void* в int* */
Чтобы заставить код компилироваться как на Си, так и на C++, необходимо использовать явное приведение типа следующим образом (с некоторыми предостережениями в отношении обоих языков):
void *ptr; int *i = (int *)ptr; int *j = (int *)malloc(5 * sizeof *j);
C++ имеет более сложные правила присваивания указателей, которые добавляют квалификаторы, поскольку C++ позволяет приводить int ** к const int *const *, но не допускает небезопасного присваивания const int **, в то время как Си не допускает ни того, ни другого (хотя компиляторы обычно выдают только предупреждение).
C++ изменяет некоторые функции стандартной библиотеки языка Си, добавляя дополнительные перегруженные функции с квалификатором типа const, например, strchr возвращает char* в Си, в то время как C++ поступает так, как если бы существовали две перегруженные функции const char *strchr(const char *) и char *strchr(char *).
C++ также более строг в преобразованиях в перечисления: целые числа не могут быть неявно преобразованы в перечисления, как в Си. Кроме того, константные перечисления (enum enumerators) всегда имеют тип int в C, тогда как в C++ они являются различными типами и могут иметь размер, отличный от размера int.
В C++ const-переменная должна быть инициализирована; в Си это необязательно.
Компиляторы C++ запрещают goto или switch пересекать инициализацию, как в следующем коде на C99: void fn(void) { goto flack; int i = 1; flack:
Несмотря на синтаксическую корректность, функция longjmp() приводит к неопределённому поведению в C++, если пропущенные () фреймы стека содержат объекты с нетривиальными деструкторами. Имплементация C++ может произвольно определять поведение таким образом, чтобы деструкторы вызывались. Однако это исключает некоторые варианты использования longjmp(), которые в противном случае были бы допустимы, например, реализация потоков или сопрограмм, переключающихся между отдельными стеками вызовов с помощью longjmp() — при переходе из нижнего стека вызовов в верхний в глобальном адресном пространстве деструкторы вызывались бы для каждого объекта в нижнем стеке вызовов. В Си такой проблемы не существует.
Си допускает несколько предварительных определений одной глобальной переменной в одной единице трансляции, что недопустимо в C++, так как это нарушение правила одного определения (). int N; int N = 10;
В Си допустимо объявление нового типа с тем же именем, что и у struct, union или enum, но это недопустимо в C++, потому что в Си типы struct, union и enum должны указываться всякий раз, когда на этот тип ссылаются, тогда как в C++ все объявления таких типов неявно содержат typedef. enum BOOL {FALSE, TRUE}; typedef int BOOL;
Объявления функций без прототипов (в стиле Кернигана — Ритчи) недопустимы в C++; они по-прежнему действительны в Си, хотя были признаны устаревшими с момента первой стандартизации Си в 1990 году. «Устаревший» () — это термин, которому даётся определение в стандарте ISO C, он означает языковую возможность, которая «может быть удалена в будущих версиях» стандарта. Аналогично, неявные объявления функций (использование функций, которые не были объявлены) не допускаются в C++ и являются недопустимыми в Си с 1999 года.
В Си прототип функции без аргументов, например int foo();, подразумевает, что аргументы не указаны. Следовательно, допустимо вызывать такую функцию с одним или несколькими аргументами , например foo(42, "hello world"). Напротив, в C++ прототип функции без аргументов означает, что функция не принимает аргументов, и вызов такой функции с аргументами является некорректным. В C правильный способ объявить функцию, которая не принимает аргументов, — это использовать 'void', как в int foo(void);, это также допустимо в C++. Пустые прототипы функций являются устаревшей () возможностью в C99 (как и в C89).
Как на Си, так и на C++ можно определить вложенные типы struct, но область действия интерпретируется по-разному: в C++ вложенный тип struct определяется только в пределах области видимости/пространства имён внешнего типа struct, тогда как в C внутренняя структура также определяется вне внешней структуры.
Си позволяет объявлять типы struct, union и enum в прототипах функций, в то время как C++ этого не делает.
C99 и C11 добавили в Си несколько дополнительных возможностей, которые не были включены в стандартный C++, таких как комплексные числа, массивы переменной длины (при этом комплексные числа и массивы переменной длины обозначены как необязательные расширения в C11), , ключевое слово restrict, квалификаторы параметров массива, составные литералы () и .
Комплексная арифметика с использованием примитивных типов данных float complex и double complex были добавлены в стандарт C99 с помощью ключевого слова _Complex и макроса complex для удобства. В C++ арифметические действия с комплексными числами могут быть выполнены с использованием класса комплексных чисел, но эти два метода несовместимы на уровне кода (однако стандарты, начиная с C++11, требуют бинарной совместимости.)
Массивы переменной длины в C99 приводят к возможному вызову оператора sizeof не во время компиляции: 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[]; }
Квалификатор типа restrict, определённый в C99, не был включён в стандарт C++03, но большинство основных компиляторов, таких как GCCRestricted Pointers from Using the GNU Compiler Collection (GCC), Microsoft Visual C++ и Intel C++ Compiler, предоставляет аналогичную функциональность в качестве расширения.
Квалификаторы параметров массива в функциях поддерживаются в Си, но не в 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 = { = 'a', = 'g' }; // Допустимо в C, но не в C++
Функции, которые не возвращают значения, могут быть отмечены с помощью атрибута noreturn в C++, тогда как C использует другое ключевое слово.
C++ добавляет множество дополнительных ключевых слов для поддержки своих новых возможностей. Это делает код на C, использующий эти ключевые слова для идентификаторов, недопустимым в C++. Например, такой код:
struct template { int new; struct template* class;
является допустимым кодом на Си, но отклоняется компилятором C++, поскольку ключевые слова template, new иclass зарезервированы.
Конструкции, которые ведут себя по-разному в Си и C++ Существует несколько синтаксических конструкций, которые допустимы как в Си, так и в C++, но дают разные результаты в этих языках.