Участник:Joparino/Совместимость C и C++
Языки программирования C и C++ тесно связаны, но имеют существенные различия. C++ создавался как потомок достандартизированного C и был разработан так, чтобы быть в основном совместимым с исходным кодом C того времени.[1][2] В связи с этим, средства разработки для двух языков (такие, как интегрированные среды разработки и компиляторы) часто интегрировались в один продукт, при этом программист может указать C или C++ в качестве исходного языка.
Однако, C не является подмножеством C++,[3], поэтому нетривиальные программы на C не будут компилироваться на C++ без изменений. Также, C++ вводит множество возможностей, недоступных в C и на практике почти весь код, написанный на C++, не соответствует коду на C. Однако в этой статье основное внимание уделяется различиям, которые приводят к тому, что соответствующий код C является неправильно написанным (англ. ill-formed) кодом C++ или соответствующим/хорошо написанном (англ. conforming/ill-formed) на обоих языках, но вести себя по-разному на C и C++.
Бьёрн Страуструп, создатель C++, предложил[4] что несовместимость между C и C++ должна быть уменьшена насколько это возможно, чтобы обеспечить максимальную совместимость между двумя языками. Другие утверждают, что, поскольку C и C++ — это два разных языка, совместимость между ними полезна, но не жизненно важна; согласно им, усилия по уменьшению несовместимости не должны препятствовать попыткам улучшить каждый язык в отдельности. Официальное обоснование стандарта 1999 C (C99) "одобрить принцип сохранения наибольшего общего подмножества" между C и C++ "сохраняя при этом различие между ними и позволяя им развиваться отдельно", и заявил, что авторы были "довольны тем, что C++ стал большим и амбициозным языком."[5]
Некоторые нововведения C99 не поддерживаются в текущем стандарте C++ или конфликтуют с особенностями C++, например массивы переменной длины, собственные комплексные типы данных и квалификатор типа restrict
. С другой стороны, C99 уменьшил некоторые другие несовместимости по сравнению с C89, включив такие функции C++, как //
комментарии и смешанные объявление и код.[6]
Конструкции, допустимые в C, но не в C++
C++ применяет более строгие правила типизации (никаких неявных нарушений системы статических типов[1]), и требования к инициализации (принудительное выполнение во время компиляции, чтобы переменные в области видимости не нарушали инициализацию)[7] чем C, и поэтому некоторый допустимый код C недопустим в C++. Обоснование этого приведено в Приложении C.1 к стандарту ISO C++.[8]
- Одно из часто встречающихся отличий заключается в том, что C более слабо типизирован в отношении указателей. В частности, C позволяет присваивать указатель
void*
любому типу указателя без приведения, в то время как C++ этого не позволяет; эта идиома часто встречается в коде C, использующем для выделения памятиmalloc
,[9] или при передаче контекстных указателей на POSIX pthreads API, и другие фреймворки, включающие обратные вызовы. Например, следующее допустимо в C, но не в C++:void *ptr; /* Неявное преобразование из void* в int* */ int *i = ptr;
или аналогично:
int *j = malloc(5 * sizeof *j); /* Неявное преобразование из void* в int* */
Чтобы заставить код компилироваться как на C, так и на C++, необходимо использовать явное приведение типа следующим образом (с некоторыми оговорками на обоих языках[10][11]):
void *ptr; int *i = (int *)ptr; int *j = (int *)malloc(5 * sizeof *j);
- C++ имеет более сложные правила присваивания указателей, которые добавляют квалификаторы, поскольку он позволяет приводить
int **
кconst int *const *
, но не небезопасное присваиваниеconst int **
, в то время как C не допускает ни того, ни другого (хотя компиляторы обычно выдают только предупреждение). - C++ изменяет некоторые функции стандартной библиотеки языка Си, добавляя дополнительные перегруженные функции с квалификатором типа
const
, напримерstrchr
возвращаетchar*
в C, в то время как C++ поступает так, как если бы были две перегруженные функцииconst char *strchr(const char *)
иchar *strchr(char *)
. - C++ также более строг в преобразованиях в перечисления: целые числа не могут быть неявно преобразованы в перечисления, как в C. Кроме того, [[Перечисляемый тип#C++
|константные перечисления]] (
enum
enumerators) всегда имеют типint
в C, тогда как в C++ они являются различными типами и могут иметь размер, отличный от размераint
. - В C++ переменная
const
должна быть инициализирована; в C это необязательно. - Компиляторы C++ запрещают goto или switch пересекать инициализацию, как в следующем коде C99:
void fn(void) { goto flack; int i = 1; flack: ; }
- Несмотря на синтаксическую корректность, функция
longjmp()
приводит к неопределенному поведению в C++, если фреймы стека с перепрыгиванием включают объекты с нетривиальными деструкторами.[12] Реализация C++ может свободно определять поведение таким образом, чтобы вызывались деструкторы. Однако это мешает некоторым видам использованияlongjmp()
, которые в противном случае были бы допустимы, такие как потоков или сопрограмм переключающихся между отдельными стеками вызовов с помощьюlongjmp()
— при переходе из нижнего стека вызовов в верхний в глобальном адресном пространстве деструкторы будут вызываться для каждого объекта в нижнем стеке вызовов. В C такой проблемы не существует. - C допускает несколько предварительных определений одной глобальной переменной в одной единице трансляции, что недопустимо в C++, так как нарушение правила одного определения (англ. One Definition Rule, ODR).
int N; int N = 10;
- В C допустимо объявление нового типа с тем же именем, что и у
struct
,union
илиenum
, но это недопустимо в C++, потому что в C типы,struct
,union
, иenum
должны указываться всякий раз, когда на тип ссылаются, тогда как в C++ все объявления таких типов неявно содержат typedef.enum BOOL {FALSE, TRUE}; typedef int BOOL;
- Объявления функций, не являющихся прототипами (в стиле «K&R»), недопустимы в C++; они по-прежнему действительны в C,[13] хотя они были признаны устаревшими с момента первоначальной стандартизации C в 1990 году. (термин «устаревший» — это определенный термин в стандарте ISO C, означающий функцию, которая «может быть рассмотрена для изъятия в будущих версиях» стандарта.) Аналогично, неявные объявления функций (с использованием функций, которые не были объявлены) не допускаются в C++ и являются недопустимыми в C с 1999 года.
- В C прототип функции без параметров, например
int foo();
, подразумевает, что параметры не указаны. Следовательно, законно вызывать такую функцию с одним или несколькими параметрами, напримерfoo(42, "hello world")
. Напротив, в C++ прототип функции без аргументов означает, что функция не принимает аргументов, и вызов такой функции с аргументами является некорректным. В C правильный способ объявить функцию, которая не принимает аргументов, - это использовать 'void', как вint foo(void);
, это также допустимо в C++. Пустые прототипы функций являются устаревшей функцией в C99 (как и в C89). - Как на C, так и на C++, можно определить типы вложенных
struct
, но область действия интерпретируется по-разному: в C++ вложеннаяstruct
определяется только в пределах области/пространства имен внешнейstruct
, тогда как в C внутренняя структура также определяется вне внешней структуры. - C позволяет объявлять типы
struct
,union
, иenum
в прототипах функций, в то время как C++ этого не делает.
C99 и C11 добавили в C несколько дополнительных возможностей, которые не были включены в стандартный C++, таких как комплексные числа, массивы переменной длины (обратите внимание, что комплексные числа и массивы переменной длины обозначены как необязательные расширения в C11), гибкий элемент массива (англ. flexible array member), ключевое слово restrict, квалификаторы параметров массива, составные литералы (англ. compound literals), и назначенные инициализаторы (англ. designated initializers).
- Комплексная арифметика с использованием примитивных типов данных
float complex
иdouble complex
были добавлены в стандарте C99 с помощью ключевого слова_Complex
и удобного макросаcomplex
. В C++ комплексная арифметика может быть выполнена с использованием класса комплексных чисел, но эти два метода несовместимы с кодом. (Однако стандарты, начиная с C++11, требуют двоичной совместимости.)[14] - Массивы переменной длины. Эта особенность приводит к возможному отсутствию оператора sizeof во время компиляции.[15]
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 с более чем одним элементом может быть гибким элементом массива (англ. flexible array member), который принимает синтаксическую форму массива с неопределенной длиной. Это служит цели, аналогичной массивам переменной длины, но очень большой массив (англ. Very Large Array, VLA) не могут отображаться в определениях типов, и, в отличие от очень большого массива, элементы гибкого массива не имеют определенного размера. ISO C++ не имеет такой особенности. Пример:
struct X { int n, m; char bytes[]; }
- Квалификатор типа
restrict
type qualifier, определенный в C99, не был включен в стандарт C++03, но большинство основных компиляторов, таких как GNU Compiler Collection,[16] Microsoft Visual C++, и Intel C++ Compiler , предоставляют аналогичную функциональность в качестве расширения. - Квалификаторы параметров массива в функциях поддерживаются в C, но не в C++
int foo(int a[const]); // аналогично int *const a int bar(char s[static 5]); // отмечает, что s имеет длину не менее 5 символов
- Функциональность составных литералов в C обобщается как на встроенные, так и на пользовательские типы синтаксисом инициализации списка C++11, хотя и с некоторыми синтаксическими и семантическими различиями.
struct X a = (struct X){4, 6}; // Аналогичным в C++ было бы X{4, 6}. Синтаксическая форма C, используемая в C99, поддерживается в качестве расширения в компиляторах GCC и Clang C++. foo(&(struct X){4, 6}); // Объект выделяется в стеке, и его адрес может быть передан функции. Это не поддерживается в C++. if (memcmp(d, (int []){8, 6, 7, 5, 3, 0, 9}, n) == 0) {} // Аналогичным в C++ было бы использование цифр = int []; if (memcmp(d, digits{8, 6, 7, 5, 3, 0, 9}, n) == 0) {}
- Назначенные инициализаторы для массивов допустимы только в C:
char s[20] = { [0] = 'a', [8] = 'g' }; // допустимо в C, но не в C++
- Функции, которые не возвращают, могут быть отмечены с помощью атрибута noreturn в C++, тогда как C использует ключевое слово distinct.
C++ добавляет множество дополнительных ключевых слов для поддержки своих новых возможностей. Это делает код C, использующий эти ключевые слова для идентификаторов, недопустимым в C++. Например:
struct template
{
int new;
struct template* class;
};
- является допустимым кодом C, но отклоняется компилятором C++, поскольку ключевые слова
template
,new
иclass
зарезервированы.
Конструкции, которые ведут себя по-разному в C и C++
Существует несколько синтаксических конструкций, которые допустимы как в C, так и в C++, но дают разные результаты в этих двух языках.
C++, однако, требует, чтобы если функция с внешней связью была объявлена inline
в любой единице перевода, то она должна быть объявлена (и, следовательно, также определена) в каждой единице перевода, где она используется, и чтобы все определения этой функции были идентичны, следуя УСО.
- Символьные литералы (англ. Character literal), такие как
'a'
, имеют типint
в C и типchar
в C++, что означает, чтоsizeof 'a'
обычно дает разные результаты на двух языках: в C++ это будет1
, в то время как в C это будетsizeof(int)
. Как еще одно следствие этого различия в типах, в C'a'
всегда будет выражением со знаком, независимо от того, является ли char знаковым или беззнаковым, , тогда как для C++ это зависит от реализации компилятора. - C++ присваивает внутреннюю связь переменным
const
в области пространства имен, если они явно не объявленыextern
, в отличие от C, в которомextern
является значением по умолчанию для всех объектов в области файлов. Заметим, что на практике это не приводит к скрытым семантическим изменениям между идентичным кодом C и C++, но вместо этого приведет к ошибке компиляции или связывания. - В C использование встроенных функций требует ручного добавления объявления прототипа функции с использованием ключевого слова
extern
ровно в одной единице трансляции, чтобы гарантировать, что не встроенная версия связана, тогда как C++ обрабатывает это автоматически. Более подробно, C различает два вида определений встроенных функций: обычные внешние определения (где явно используетсяextern
) и встроенные определения. C++, с другой стороны, предоставляет только встроенные определения для встроенных функций. В C встроенное определение аналогично внутреннему (т.е. статическому) определению в том смысле, что оно может сосуществовать в одной и той же программе с одним внешним определением и любым количеством внутренних и встроенных определений одной и той же функции в других единицах трансляции, все из которых могут отличаться. Это отдельное соображение от связи функции, но не независимое. Компиляторам C предоставляется свобода выбора между использованием встроенных и внешних определений одной и той же функции, когда оба они видны. C++, однако, требует, чтобы если функция с внешней связью была объявленаinline
в любой единице трансляции, то она должна быть объявлена (и, следовательно, также определена) в каждой единице трансляции, где она используется, и чтобы все определения этой функции были идентичны, следуя правилу одного определения. Обратите внимание, что статические встроенные функции ведут себя одинаково в C и 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)
*/
}
Это связано с тем, что C требует struct
перед тегами структуры (и поэтому sizeof(T)
ссылается на переменную), но C++ позволяет его опустить (и поэтому sizeof(T)
ссылается на неявный typedef
). Имейте в виду, что результат отличается, когда объявление extern
помещается внутри функции: тогда наличие идентификатора с тем же именем в области действия функции препятствует вступлению в силу неявного typedef
для C++, и результат для C и C++ будет одинаковым. Обратите также внимание, что двусмысленность в приведенном выше примере связана с использованием круглой скобки с оператором sizeof
. Использование sizeof T
ожидало бы, что T
будет выражением, а не типом, и, следовательно, пример не будет компилироваться с C++.
Linking C and C++ code
While C and C++ maintain a large degree of source compatibility, the object files their respective compilers produce can have important differences that manifest themselves when intermixing C and C++ code. Notably:
- C compilers do not name mangle symbols in the way that C++ compilers do.[17]
- Depending on the compiler and architecture, it also may be the case that calling conventions differ between the two languages.
For these reasons, for C++ code to call a C function foo()
, the C++ code must prototype foo()
with extern "C"
. Likewise, for C code to call a C++ function bar()
, the C++ code for bar()
must be declared with extern "C"
.
A common practice for header files to maintain both C and C++ compatibility is to make its declaration be extern "C"
for the scope of the header:[18]
/* Header file foo.h */
#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */
extern "C" {
#endif
/* These functions get C linkage */
void foo();
struct bar { /* ... */ };
#ifdef __cplusplus /* If this is a C++ compiler, end C linkage */
}
#endif
Differences between C and C++ linkage and calling conventions can also have subtle implications for code that uses function pointers. Some compilers will produce non-working code if a function pointer declared extern "C"
points to a C++ function that is not declared extern "C"
.[19]
For example, the following code:
void my_function();
extern "C" void foo(void (*fn_ptr)(void));
void bar()
{
foo(my_function);
}
Using Sun Microsystems' C++ compiler, this produces the following warning:
$ 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(*)().
This is because my_function()
is not declared with C linkage and calling conventions, but is being passed to the C function foo()
.
References
- ↑ 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 года.
- ↑ B.Stroustrup. C and C++: Siblings. The C/C++ Users Journal. July 2002. Дата обращения: 17 марта 2019.
- ↑ Bjarne Stroustrup's FAQ – Is C a subset of C++? Дата обращения: 22 сентября 2019.
- ↑ B. Stroustrup. C and C++: A Case for Compatibility. The C/C++ Users Journal. August 2002. Дата обращения: 18 августа 2013. Архивировано 22 июля 2012 года.
- ↑ Rationale for International Standard—Programming Languages—C Архивировано 6 июня 2016 года., revision 5.10 (April 2003).
- ↑ C Dialect Options - Using the GNU Compiler Collection (GCC) . gnu.org. Архивировано 26 марта 2014 года.
- ↑ 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.")
- ↑ N4659: Working Draft, Standard for Programming Language C++ . Архивировано 7 декабря 2017 года.
- ↑ IBM Knowledge Center . ibm.com.
- ↑ FAQ > Casting malloc - Cprogramming.com . faq.cprogramming.com. Архивировано 5 апреля 2007 года.
- ↑ 4.4a — Explicit type conversion (casting) (16 апреля 2015). Архивировано 25 сентября 2016 года.
- ↑ longjmp - C++ Reference . www.cplusplus.com. Архивировано 19 мая 2018 года.
- ↑ 2011 ISO C draft standard .
- ↑ std::complex - cppreference.com . en.cppreference.com. Архивировано 15 июля 2017 года.
- ↑ Incompatibilities Between ISO C and ISO C++ . Архивировано 9 апреля 2006 года.
- ↑ Restricted Pointers Архивировано 6 августа 2016 года. from Using the GNU Compiler Collection (GCC)
- ↑ IBM Knowledge Center . ibm.com.
- ↑ IBM Knowledge Center . ibm.com.
- ↑ Oracle Documentation . Docs.sun.com. Дата обращения: 18 августа 2013. Архивировано 3 апреля 2009 года.
External links
- Detailed comparison, sentence by sentence, from a C89 Standard perspective.
- Incompatibilities Between ISO C and ISO C++, David R. Tribble (August 2001).
- Oracle (Sun Microsystems) C++ Migration Guide, section 3.11, Oracle/Sun compiler docs on linkage scope.
- Oracle: Mixing C and C++ Code in the Same Program, overview by Steve Clamage (ANSI C++ Committee chair).