Русская Википедия:Совместимость C и C++
Языки программирования C и C++ тесно связаны, но имеют существенные различия. C++ создавался как потомок достандартизированного C, по большей части совместимый с ним на тот момент на уровне исходного кода и компоновки[1][2]. В связи с этим средства разработки для обоих языков (такие, как среды разработки и компиляторы) часто интегрируются в один продукт, при этом программист может выбрать C или C++ в качестве языка исходного кода.
Однако, C не является подмножеством C++[3], поэтому нетривиальные программы на C не будут компилироваться на C++ без изменений. Также C++ вводит множество возможностей, недоступных в C, и на практике почти весь код, написанный на C++, не соответствует коду на C. Однако в этой статье основное внимание уделяется различиям, которые приводят к тому, что соответствующий код C является неправильно написанным (Шаблон:Lang-en) кодом на C++ или соответствующим/хорошо написанным (Шаблон:Lang-en) на обоих языках, но может вести себя по-разному на C и C++.
Бьёрн Страуструп, создатель C++, предложил[4] что несовместимость между C и C++ должна быть уменьшена насколько это возможно, чтобы обеспечить максимальное взаимодействие между двумя языками. Другие утверждают, что, поскольку C и C++ — это два разных языка, совместимость между ними полезна, но не жизненно важна; согласно их мнению, усилия по уменьшению несовместимости не должны препятствовать попыткам улучшить каждый язык в отдельности. Третьи утверждают, что почти каждая синтаксическая ошибка, которую можно допустить в Си, была пересмотрена в C++ таким образом, чтобы порождать компилируемый, хоть не обязательно корректный код[5]. Официальное обоснование стандарта C 1999 года (C99) «поддерживает принцип сохранения наибольшего общего подмножества» между C и C++, «сохраняет при этом различия между ними и позволяет развиваться отдельно», там также утверждается, что авторы были «довольны тем, что C++ стал большим и амбициозным языком»[6].
Некоторые нововведения C99 не поддерживаются в текущем стандарте C++ или конфликтуют с отдельными возможностями C++, например, массивы переменной длины, собственные комплексные типы данных и квалификатор типа restrict
. С другой стороны, C99 уменьшил некоторые другие несовместимости по сравнению с C89, включив такие функции C++, как однострочные комментарии //
, а также смешение объявлений и кода[7].
Конструкции, допустимые в C, но не в C++
C++ применяет более строгие правила типизации (никаких неявных нарушений системы статических типов[1]) и требования к инициализации (принудительная проверка во время компиляции, что у переменных в области видимости не нарушена инициализация, то есть невозможно вернуться к месту до объявления с явной или неявной инициализацией, если не считать блоки, в которые не управляющий поток не попадал)[8], и поэтому некоторый допустимый код C недопустим в C++. Обоснование этого приведено в Приложении C.1 к стандарту ISO C++[9].
C99 и C11 добавили в C несколько дополнительных возможностей, которые не были включены в стандартный C++, таких как комплексные числа, массивы переменной длины (обратите внимание, что комплексные числа и массивы переменной длины обозначены как необязательные расширения в C11), Шаблон:Нп5, ключевое слово restrict, квалификаторы параметров массива, составные литералы (Шаблон:Lang-en) и Шаблон:Нп5.
C++ добавляет множество дополнительных ключевых слов для поддержки своих новых возможностей. Это делает код на C, использующий эти ключевые слова для идентификаторов, недопустимым в C++. Например, такой код:
struct template
{
int new;
struct template* class;
};
- является допустимым кодом на C, но отклоняется компилятором C++, поскольку ключевые слова
template
,new
иclass
зарезервированы.
Конструкции, которые ведут себя по-разному в C и C++
Существует несколько синтаксических конструкций, которые допустимы как в C, так и в C++, но дают разные результаты в этих языках.
- Шаблон:Нп5, такие как
'a'
, имеют типint
в C и типchar
в C++, это означает, чтоsizeof 'a'
обычно даёт разные результаты на двух языках: в C++ это будет1
, в то время как в C это будетsizeof(int)
. Как ещё одно следствие этого различия в типах, в C'a'
всегда будет выражением со знаком, независимо от того, являетсяchar
знаковым или беззнаковым, тогда как для C++ это зависит от реализации компилятора (Шаблон:Lang-en). - C++ использует внутреннюю компоновку
const
-переменных в области пространства имён, если только они явно не объявлены какextern
, в отличие от C, в которомextern
является вариантом по умолчанию для всех сущностей, имеющих область видимости — файл (Шаблон:Lang-en). Заметим, что на практике это не приводит к скрытым семантическим изменениям между идентичным кодом C и C++, но вместо этого приведёт к ошибке компиляции или компоновки. - В C использование встроенных функций требует, чтобы объявление прототипа функции с использованием ключевого слова
extern
было вручную добавлено ровно в одну единицу трансляции, чтобы гарантировать, что не-inline
версия скомпонована, тогда как 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++.
Связывание кода C и C++
В то время как C и C++ поддерживают высокую степень совместимости исходных текстов, объектные файлы, создаваемые их компиляторами, могут иметь важные различия, которые проявляются при смешивании кода C и C++. Важные особенности:
- Компиляторы C не выполняют Шаблон:Нп5 символов, как это делают компиляторы C++[10].
- В зависимости от компилятора и архитектуры соглашения о вызовах могут различаться между языками.
Чтобы код на C++ вызывал функцию на C foo()
, код на C++ должен создавать прототип foo()
с помощью extern "C"
. Аналогично, чтобы код на C вызывал функцию на C++ bar()
, код C++ для bar()
должен быть объявлен с extern "C"
.
Обычная практика в заголовочных файлах для поддержания совместимости как с C, так и C++ — добавлять в них объявление с extern "C"
для всей области видимости заголовка[11]:
/* Заголовочный файл foo.h */
# ifdef __cplusplus /* Если это компилятор C++, использовать компоновку, как в C */
extern "C" {
# endif
/* У этих функций компоновка, как в языке C */
void foo();
struct bar { /* ... */ };
# ifdef __cplusplus /* Если это компилятор C++, завершите использование компоновки, как в C */
}
# endif
Различия между соглашениями о компоновке и вызовах C и C++ также могут иметь некие последствия для кода, использующего указатели на функции. Некоторые компиляторы дадут нерабочий код, если указатель на функцию, объявленный как extern "C"
, указывает на функцию из C++, которая не объявлена как extern "C"
[12].
Например, следующий код:
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, но передаётся C-функции foo()
.
Примечания
Ссылки
- 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).
- ↑ 1,0 1,1 Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ см. The UNIX-HATERS Handbook, с.208
- ↑ Rationale for International Standard—Programming Languages—C Шаблон:Webarchive, revision 5.10 (April 2003).
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web («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.»)
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web
- ↑ Шаблон:Cite web