Для установки нажмите кнопочку Установить расширение. И это всё.

Исходный код расширения WIKI 2 регулярно проверяется специалистами Mozilla Foundation, Google и Apple. Вы также можете это сделать в любой момент.

4,5
Келли Слэйтон
Мои поздравления с отличным проектом... что за великолепная идея!
Александр Григорьевский
Я использую WIKI 2 каждый день
и почти забыл как выглядит оригинальная Википедия.
Статистика
На русском, статей
Улучшено за 24 ч.
Добавлено за 24 ч.
Альтернативы
Недавние
Show all languages
Что мы делаем. Каждая страница проходит через несколько сотен совершенствующих техник. Совершенно та же Википедия. Только лучше.
.
Лео
Ньютон
Яркие
Мягкие

Несовместимость Си и 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++, например, массивы переменной длины, собственные комплексные типы данных и квалификатор типа restrict. С другой стороны, C99 уменьшил некоторые другие несовместимости по сравнению с C89, включив такие функции C++, как однострочные комментарии //, а также смешение объявлений и кода[7].

Конструкции, допустимые в Си, но не в C++

C++ применяет более строгие правила типизации (никаких неявных нарушений системы статических типов[1]) и требования к инициализации (принудительная проверка во время компиляции, что у переменных в области видимости не нарушена инициализация, то есть невозможно вернуться к месту до объявления с явной или неявной инициализацией, если не считать блоки, в которые не управляющий поток не попадал)[8], и поэтому некоторый допустимый код Си недопустим в C++. Обоснование этого приведено в Приложении C.1 к стандарту ISO C++[9].

Одно из часто встречающихся отличий заключается в том, что Си более слабо типизирован в отношении указателей. В частности, Си позволяет присваивать указатель void* любому типу указателя без приведения, в то время как C++ этого не позволяет; эта идиома часто встречается в коде на Си, использующем для выделения памяти malloc[10], или при передаче контекстных указателей в pthreads (POSIX API) и другие фреймворки, использующие обратные вызовы. Например, следующее допустимо в Си, но не в C++:

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++ позволяет приводить 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++, если пропущенные (англ. jumped-over) фреймы стека содержат объекты с нетривиальными деструкторами[13]. Имплементация C++ может произвольно определять поведение таким образом, чтобы деструкторы вызывались. Однако это исключает некоторые варианты использования longjmp(), которые в противном случае были бы допустимы, например, реализация потоков или сопрограмм, переключающихся между отдельными стеками вызовов с помощью longjmp() — при переходе из нижнего стека вызовов в верхний в глобальном адресном пространстве деструкторы вызывались бы для каждого объекта в нижнем стеке вызовов. В Си такой проблемы не существует.

Си допускает несколько предварительных определений одной глобальной переменной в одной единице трансляции, что недопустимо в C++, так как это нарушение правила одного определения (англ. One Definition Rule, ODR).

int N;
int N = 10;

В Си допустимо объявление нового типа с тем же именем, что и у struct, union или enum, но это недопустимо в C++, потому что в Си типы struct, union и enum должны указываться всякий раз, когда на этот тип ссылаются, тогда как в C++ все объявления таких типов неявно содержат typedef.

enum BOOL {FALSE, TRUE};
typedef int BOOL;

Объявления функций без прототипов (в стиле Кернигана — Ритчи) недопустимы в C++; они по-прежнему действительны в Си[14], хотя были признаны устаревшими с момента первой стандартизации Си в 1990 году. «Устаревший» (англ. obsolescent) — это термин, которому даётся определение в стандарте ISO C, он означает языковую возможность, которая «может быть удалена в будущих версиях» стандарта. Аналогично, неявные объявления функций (использование функций, которые не были объявлены) не допускаются в C++ и являются недопустимыми в Си с 1999 года.

В Си прототип функции без аргументов, например int foo();, подразумевает, что аргументы не указаны. Следовательно, допустимо вызывать такую функцию с одним или несколькими аргументами , например foo(42, "hello world"). Напротив, в C++ прототип функции без аргументов означает, что функция не принимает аргументов, и вызов такой функции с аргументами является некорректным. В C правильный способ объявить функцию, которая не принимает аргументов, — это использовать 'void', как в int foo(void);, это также допустимо в C++. Пустые прототипы функций являются устаревшей (англ. deprecated) возможностью в C99 (как и в C89).

Как на Си, так и на C++ можно определить вложенные типы struct, но область действия интерпретируется по-разному: в C++ вложенный тип struct определяется только в пределах области видимости/пространства имён внешнего типа struct, тогда как в C внутренняя структура также определяется вне внешней структуры.

Си позволяет объявлять типы struct, union и enum в прототипах функций, в то время как C++ этого не делает.

C99 и C11 добавили в Си несколько дополнительных возможностей, которые не были включены в стандартный C++, таких как комплексные числа, массивы переменной длины (при этом комплексные числа и массивы переменной длины обозначены как необязательные расширения в C11), гибкий элемент массива[англ.], ключевое слово restrict, квалификаторы параметров массива, составные литералы (англ. compound literals) и назначенные инициализаторы[англ.].

Комплексная арифметика с использованием примитивных типов данных float complex и double complex были добавлены в стандарт C99 с помощью ключевого слова _Complex и макроса complex для удобства. В C++ арифметические действия с комплексными числами могут быть выполнены с использованием класса комплексных чисел, но эти два метода несовместимы на уровне кода (однако стандарты, начиная с C++11, требуют бинарной совместимости.)[15]

Массивы переменной длины в C99 приводят к возможному вызову оператора sizeof не во время компиляции[16]:

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, но большинство основных компиляторов, таких как GCC[17], 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[20] = { [0] = 'a', [8] = 'g' };  // Допустимо в C, но не в C++

Функции, которые не возвращают значения, могут быть отмечены с помощью атрибута noreturn в C++, тогда как C использует другое ключевое слово.

C++ добавляет множество дополнительных ключевых слов для поддержки своих новых возможностей. Это делает код на C, использующий эти ключевые слова для идентификаторов, недопустимым в C++. Например, такой код:

struct template
{
    int new;
    struct template* class;
};

является допустимым кодом на Си, но отклоняется компилятором C++, поскольку ключевые слова template, new иclass зарезервированы.

Конструкции, которые ведут себя по-разному в Си и C++

Существует несколько синтаксических конструкций, которые допустимы как в Си, так и в C++, но дают разные результаты в этих языках.

Символьные литералы[англ.], такие как 'a', имеют тип int в C и тип char в C++, это означает, что sizeof 'a' обычно даёт разные результаты на двух языках: в C++ это будет 1, в то время как в C это будет sizeof(int). Как ещё одно следствие этого различия в типах, в C 'a' всегда будет выражением со знаком, независимо от того, является char знаковым или беззнаковым, тогда как для C++ это зависит от реализации компилятора (англ. implementation specific).

C++ использует внутреннюю компоновку const-переменных в области пространства имён, если только они явно не объявлены как extern, в отличие от Си, в котором extern является вариантом по умолчанию для всех сущностей, имеющих область видимости — файл (англ. file-scoped entities). Заметим, что на практике это не приводит к скрытым семантическим изменениям между идентичным кодом Си и C++, но вместо этого приведёт к ошибке компиляции или компоновки.

В Си использование встроенных функций требует, чтобы объявление прототипа функции с использованием ключевого слова extern было вручную добавлено ровно в одну единицу трансляции, чтобы гарантировать, что не-inline версия скомпонована, тогда как C++ обрабатывает это автоматически. Если точнее, Си различает два вида определений встроенных функций: обычные внешние определения (где явно используется extern) и встроенные определения. C++, с другой стороны, предоставляет только встроенные определения для встроенных функций. В Си встроенное определение аналогично внутреннему (то есть статическому) определению в том смысле, что оно может сосуществовать в одной и той же программе с одним внешним определением и любым количеством внутренних и встроенных определений одной и той же функции в других единицах трансляции, все из которых могут различаться. Это не то же самое, что компоновка функции, но не полностью независимое понятие. Компиляторам C предоставляется свобода выбора между использованием встроенных и внешних определений одной и той же функции, когда оба они доступны. C++, однако, требует, чтобы если функция с внешней компоновкой объявлена как inline в любой единице трансляции, то она должна также была объявлена (и, следовательно, также определена) в каждой единице трансляции, где используется, и чтобы все определения этой функции были идентичны по правилу одного определения. Обратите внимание, что статические встроенные функции ведут себя одинаково в Си и C++.

И C99, и C++ имеют логический тип bool с константами true и false, но они определены по-разному. В C++ bool — это встроенный тип и зарезервированное ключевое слово. В C99 новое ключевое слово _Bool вводится как новый логический тип. Заголовок stdbool.h содержит макросы bool, true и false, которые определены как _Bool, 1 и 0, соответственно. Следовательно, true и false имеют тип int в 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)
     */
}

Это связано с тем, что Си требует наличия struct перед тегами структуры (и поэтому sizeof(T) ссылается на переменную), но C++ позволяет его опустить (и поэтому sizeof(T) ссылается на неявный typedef). Результат различается, когда объявление extern помещается внутрь функции: тогда наличие идентификатора с тем же именем в области видимости функции препятствует вступлению в силу неявного typedef для C++, и результат для Си и C++ будет одинаковым. Двусмысленность в примере связана с использованием круглых скобок у оператора sizeof. При использовании sizeof T ожидалось бы, что T будет выражением, а не типом, и, следовательно, пример не будет компилироваться на C++.

Связывание кода Си и C++

В то время как Си и C++ поддерживают высокую степень совместимости исходных текстов, объектные файлы, создаваемые их компиляторами, могут иметь важные различия, которые проявляются при смешивании кода Си и C++.

Компиляторы Си не выполняют name mangling[англ.] символов, как это делают компиляторы C++[18].

В зависимости от компилятора и архитектуры соглашения о вызовах могут различаться между языками.

Чтобы код на C++ вызывал функцию на Си foo(), код на C++ должен создавать прототип foo() с помощью extern "C". Аналогично, чтобы код на C вызывал функцию на C++ bar(), код C++ для bar() должен быть объявлен с extern "C".

Обычная практика в заголовочных файлах для поддержания совместимости как с Си, так и C++ — добавлять в них объявление с extern "C" для всей области видимости заголовка[19]:

/* Заголовочный файл foo.h */
# ifdef __cplusplus /* Если это компилятор C++, использовать компоновку, как в C */
extern "C" {
# endif

/* У этих функций компоновка, как в языке C */
void foo();

struct bar { /* ... */ };

# ifdef __cplusplus /* Если это компилятор C++, завершите использование компоновки, как в C */
}
# endif

Различия между соглашениями о компоновке и вызовах Си и C++ также могут иметь некие последствия для кода, использующего указатели на функции. Некоторые компиляторы дадут нерабочий код, если указатель на функцию, объявленный как extern "C", указывает на функцию из C++, которая не объявлена как extern "C"[20].

Например, следующий код:

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(*)().

Это связано с тем, что my_function() не объявляется с помощью соглашений о компоновке и вызове языка Си, но передаётся C-функции foo().

Примечания

  1. 1 2 Stroustrup, Bjarne An Overview of the C++ Programming Language in The Handbook of Object Technology (Editor: Saba Zamir). CRC Press LLC, Boca Raton. 1999. ISBN 0-8493-3135-8. (PDF) 4. Дата обращения: 12 августа 2009. Архивировано 16 августа 2012 года.
  2. B.Stroustrup. C and C++: Siblings. The C/C++ Users Journal. July 2002. Дата обращения: 17 марта 2019. Архивировано 21 декабря 2018 года.
  3. Bjarne Stroustrup's FAQ – Is C a subset of C++? Дата обращения: 22 сентября 2019. Архивировано 6 февраля 2016 года.
  4. B. Stroustrup. C and C++: A Case for Compatibility. The C/C++ Users Journal. August 2002. Дата обращения: 18 августа 2013. Архивировано 22 июля 2012 года.
  5. см. The UNIX-HATERS Handbook, с.208
  6. Rationale for International Standard—Programming Languages—C Архивировано 6 июня 2016 года., revision 5.10 (April 2003).
  7. C Dialect Options - Using the GNU Compiler Collection (GCC). gnu.org. Архивировано 26 марта 2014 года.
  8. N4659: Working Draft, Standard for Programming Language C++. Архивировано 7 декабря 2017 года. («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.»)
  9. N4659: Working Draft, Standard for Programming Language C++. Архивировано 7 декабря 2017 года.
  10. IBM Knowledge Center. ibm.com.
  11. FAQ > Casting malloc - Cprogramming.com. faq.cprogramming.com. Архивировано 5 апреля 2007 года.
  12. 4.4a — Explicit type conversion (casting) (16 апреля 2015). Архивировано 25 сентября 2016 года.
  13. longjmp - C++ Reference. www.cplusplus.com. Архивировано 19 мая 2018 года.
  14. 2011 ISO C draft standard. Дата обращения: 28 июля 2022. Архивировано 29 марта 2018 года.
  15. std::complex - cppreference.com. en.cppreference.com. Архивировано 15 июля 2017 года.
  16. Incompatibilities Between  ISO C and ISO C++. Архивировано 9 апреля 2006 года.
  17. Restricted Pointers Архивировано 6 августа 2016 года. from Using the GNU Compiler Collection (GCC)
  18. IBM Knowledge Center. ibm.com.
  19. IBM Knowledge Center. ibm.com.
  20. Oracle Documentation. Docs.sun.com. Дата обращения: 18 августа 2013. Архивировано 3 апреля 2009 года.

Ссылки

Эта страница в последний раз была отредактирована 15 мая 2024 в 08:56.
Как только страница обновилась в Википедии она обновляется в Вики 2.
Обычно почти сразу, изредка в течении часа.
Основа этой страницы находится в Википедии. Текст доступен по лицензии CC BY-SA 3.0 Unported License. Нетекстовые медиаданные доступны под собственными лицензиями. Wikipedia® — зарегистрированный товарный знак организации Wikimedia Foundation, Inc. WIKI 2 является независимой компанией и не аффилирована с Фондом Викимедиа (Wikimedia Foundation).