Языки программирования C и C++ тесно связаны, но имеют много существенных различий. C++ начинался как ответвление раннего , достандартизированного C и был разработан так, чтобы быть в основном совместимым по исходному коду и компоновке с компиляторами C того времени. [1] [2] В связи с этим инструменты разработки для двух языков (такие как IDE и компиляторы ) часто интегрируются в один продукт, при этом программист может указать C или C++ в качестве исходного языка.
Однако C не является подмножеством C ++, [3] и нетривиальные программы C не будут компилироваться как код C++ без изменений. Аналогично, C++ вводит много функций, которые недоступны в C, и на практике почти весь код, написанный на C++, не является соответствующим кодом C. Однако в этой статье основное внимание уделяется различиям, из-за которых соответствующий код C является плохо сформированным кодом C++ или соответствующим/правильно сформированным в обоих языках, но ведет себя по-разному в C и C++.
Бьярне Страуструп , создатель C++, предположил [4] , что несовместимости между C и C++ должны быть максимально уменьшены, чтобы максимизировать взаимодействие между двумя языками. Другие утверждали, что поскольку C и C++ являются двумя разными языками, совместимость между ними полезна, но не жизненно важна; согласно этому лагерю, усилия по уменьшению несовместимости не должны препятствовать попыткам улучшить каждый язык в отдельности. Официальное обоснование стандарта C 1999 года ( C99 ) «одобряло принцип поддержания наибольшего общего подмножества» между C и C++ «при сохранении различия между ними и позволении им развиваться отдельно», и заявляло, что авторы «довольны тем, что C++ будет большим и амбициозным языком». [5]
Несколько дополнений C99 не поддерживаются в текущем стандарте C++ или конфликтуют с функциями C++, такими как массивы переменной длины , собственные комплексные типы чисел и restrict
квалификатор типа . С другой стороны, C99 уменьшил некоторые другие несовместимости по сравнению с C89, включив функции C++, такие как //
комментарии и смешанные объявления и код. [6]
C++ применяет более строгие правила типизации (без неявных нарушений статической системы типов [1] ) и требования инициализации (принудительное соблюдение во время компиляции того, что переменные в области видимости не имеют нарушенной инициализации) [7] , чем C, и поэтому часть допустимого кода C является недопустимым в C++. Обоснование этого приведено в Приложении C.1 стандарта ISO C++. [8]
void*
назначать указатель любому типу указателя без приведения, тогда как C++ — нет; эта идиома часто встречается в коде C, использующем malloc
выделение памяти, [9] или при передаче указателей контекста в API POSIX pthreads и другие фреймворки, включающие обратные вызовы . Например, следующее допустимо в C, но не в C++:void * ptr ; /* Неявное преобразование из void* в int* */ int * i = ptr ;
или аналогично:
int * j = malloc ( 5 * sizeof * j ); /* Неявное преобразование из void* в int* */
Чтобы код компилировался как на C, так и на C++, необходимо использовать явное приведение типов, как показано ниже (с некоторыми оговорками в обоих языках [10]):
void * ptr ; int * i = ( int * ) ptr ; int * j = ( int * ) malloc ( 5 * sizeof * j );
int **
, const int *const *
но не допускает небезопасное присваивание значению , const int **
в то время как в языке C не допускается ни то, ни другое (хотя компиляторы обычно выдают только предупреждение).const
квалификаторами типов , например, strchr
returns char*
в C, в то время как C++ действует так, как будто есть две перегруженные функции const char *strchr(const char *)
и a char *strchr(char *)
. В C23 используется обобщенный выбор, чтобы сделать поведение C более похожим на поведение C++. [11]enum
перечислители) всегда имеют тип int
в C, тогда как в C++ они являются отдельными типами и могут иметь размер, отличный от размера int
. [ требуется обновление ]const
переменная должна быть инициализирована; в C это не обязательно.void fn ( void ) { goto flack ; int i = 1 ; flack :; }
longjmp()
приводит к неопределенному поведению в C++, если перепрыгиваемые стековые кадры включают объекты с нетривиальными деструкторами. [12] Реализация C++ свободна определять поведение таким образом, чтобы деструкторы вызывались. Однако это исключило бы некоторые применения, longjmp()
которые в противном случае были бы допустимы, такие как реализация потоков или сопрограмм, переключающихся между отдельными стеками вызовов с longjmp()
— при переходе из нижнего в верхний стек вызовов в глобальном адресном пространстве деструкторы вызывались бы для каждого объекта в нижнем стеке вызовов. В C такой проблемы нет.целое N ; целое N = 10 ;
struct
, union
или enum
допустимо, но в C++ это недопустимо, поскольку в C, struct
, union
и enum
типы должны указываться как таковые всякий раз, когда на тип ссылаются, тогда как в C++ все объявления таких типов неявно содержат typedef .enum BOOL { ЛОЖЬ , ИСТИНА }; typedef int BOOL ;
int foo();
, подразумевает, что параметры не указаны. Поэтому допустимо вызывать такую функцию с одним или несколькими аргументами , например foo(42, "hello world")
. Напротив, в C++ прототип функции без аргументов означает, что функция не принимает аргументов, и вызов такой функции с аргументами является некорректным. В C правильный способ объявления функции, которая не принимает аргументов, — это использование 'void', как в int foo(void);
, что также допустимо в C++. Пустые прототипы функций являются устаревшей функцией в C99 (как и в C89).struct
типы, но область действия интерпретируется по-разному: в C++ вложенный тип struct
определяется только в пределах области действия/пространства имен внешней структуры struct
, тогда как в C внутренняя структура определяется также и за пределами внешней структуры.struct
, union
, и в прототипах функций, тогда как C++ этого не делает.enum
В C99 и C11 в C было добавлено несколько дополнительных функций, которые не были включены в стандарт C++ начиная с C++20 , например, комплексные числа, массивы переменной длины (комплексные числа и массивы переменной длины обозначены как необязательные расширения в C11), гибкие элементы массива , ключевое слово restrict , квалификаторы параметров массива и составные литералы .
float complex
примитивных double complex
типов данных и была добавлена в стандарт C99 с помощью _Complex
ключевого слова и complex
макроса удобства. В C++ комплексная арифметика может быть выполнена с использованием класса комплексных чисел, но эти два метода несовместимы по коду. (Однако стандарты, начиная с C++11 , требуют двоичной совместимости.) [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 ); // напечатает x*2 }
struct X { int n , m ; char bytes []; }
restrict
Квалификатор типа, определенный в C99, не был включен в стандарт C++03, но большинство основных компиляторов, таких как GNU Compiler Collection , [18] Microsoft Visual C++ и Intel C++ Compiler, предоставляют аналогичную функциональность в качестве расширения.int foo ( int a [ const ]); // эквивалентно int *const a int bar ( char s [ static 5 ]); // указывает, что s имеет длину не менее 5 символов
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++ будет использование 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 использует отдельное ключевое слово. В C23 также поддерживается синтаксис атрибута. [19]C++ добавляет множество дополнительных ключевых слов для поддержки своих новых функций. Это делает код C, использующий эти ключевые слова для идентификаторов, недействительным в C++. Например:
шаблон структуры { int new ; шаблон структуры * класс ; };
template
, new
и class
зарезервированы.Существует несколько синтаксических конструкций, которые допустимы как в C, так и в C++, но дают разные результаты в этих двух языках.
'a'
имеют тип int
в C и тип char
в C++, что означает, что, sizeof 'a'
как правило, даст разные результаты в двух языках: в C++ это будет 1
, а в C это будет sizeof(int)
. Как еще одно следствие этого различия типов, в C 'a'
всегда будет знаковым выражением, независимо от того, char
является ли это знаковым или беззнаковым типом, тогда как для C++ это зависит от реализации компилятора.const
если они явно не объявлены extern
, в отличие от C, в котором extern
это значение по умолчанию для всех сущностей области файла. На практике это не приводит к скрытым семантическим изменениям между идентичным кодом C и C++, но вместо этого приводит к ошибке компиляции или компоновки.inline
: обычные внешние определения (где extern
явно используется ) и встроенные определения. C++, с другой стороны, предоставляет только встроенные определения для встроенных функций. В C встроенное определение похоже на внутреннее (т. е. статическое) в том, что оно может сосуществовать в одной программе с одним внешним определением и любым количеством внутренних и встроенных определений той же функции в других единицах трансляции, все из которых могут отличаться. Это отдельное рассмотрение от связывания функции, но не независимое. Компиляторам C предоставляется право выбора между использованием встроенных и внешних определений одной и той же функции, когда оба они видны. Однако C++ требует, чтобы если функция с внешней связью объявлена inline
в любой единице трансляции, то она должна быть объявлена (и, следовательно, также определена) в каждой единице трансляции, где она используется, и чтобы все определения этой функции были идентичны, следуя ODR. Статические встроенные функции ведут себя одинаково в C и C++.bool
с константами true
и false
, но они определены по-разному. В C++ bool
— встроенный тип и зарезервированное ключевое слово . В C99 _Bool
в качестве нового логического типа введено новое ключевое слово . Заголовок stdbool.h
содержит макросы bool
, true
и , false
которые определены как _Bool
, 1
и 0
, соответственно. Следовательно, true
и false
имеют тип int
в C. Однако это, скорее всего, изменится в C23 , проект которого включает изменение bool
, true
, и false
на ключевые слова и предоставление true
и false
типа bool
.int
тогда как в C++ оно всегда знаковое, чтобы соответствовать базовому типу.Некоторые другие отличия от предыдущего раздела также могут быть использованы для создания кода, который компилируется на обоих языках, но ведет себя по-разному. Например, следующая функция вернет разные значения в C и C++:
внешний int T ; int size ( void ) { struct T { int i ; int j ; }; return sizeof ( T ); /* C: return sizeof(int) * C++: return 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 foo()
, код C++ должен быть прототипирован foo()
с помощью extern "C"
. Аналогично, для кода C, чтобы вызвать функцию C++ bar()
, код C++ для bar()
должен быть объявлен с помощью extern "C"
.
Распространенной практикой для файлов заголовков, чтобы поддерживать совместимость как с C, так и с C++, является объявление extern "C"
области действия заголовка: [21]
/* Заголовочный файл 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"
. [22]
Например, следующий код:
void моя_функция (); extern "C" void foo ( void ( * fn_ptr )( void )); пустая строка () { foo ( моя_функция );}
При использовании компилятора C++ компании Sun Microsystems выводится следующее предупреждение:
$ CC - c test . cc "test.cc" , строка 6 : Предупреждение ( Анахронизм ) : Формальный аргумент fn_ptr типа extern " C " void ( * ) ( ) при вызове foo ( extern " C " void ( * )()) передается void ( * ) ( ).
Это происходит потому, что my_function()
не объявлено с соглашениями о связывании и вызовах языка C, а передается функции языка foo()
C.