Совместимость C и C++

Сравнение языков программирования

Языки программирования 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, но не в C++

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

  • Одно из часто встречающихся отличий заключается в том, что C более слабо типизирован в отношении указателей. В частности, C позволяет 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 );            
  • В языке C++ действуют более сложные правила присвоения указателей, в которые добавляются квалификаторы, поскольку он допускает присваивание значению int **, const int *const *но не допускает небезопасное присваивание значению , const int **в то время как в языке C не допускается ни то, ни другое (хотя компиляторы обычно выдают только предупреждение).
  • C++ изменяет некоторые функции стандартной библиотеки C , чтобы добавить дополнительные перегруженные функции с const квалификаторами типов , например, strchrreturns char*в C, в то время как C++ действует так, как будто есть две перегруженные функции const char *strchr(const char *)и a char *strchr(char *). В C23 используется обобщенный выбор, чтобы сделать поведение C более похожим на поведение C++. [11]
  • C++ также более строг в преобразованиях в перечисления: целые числа не могут быть неявно преобразованы в перечисления, как в C. Кроме того, константы перечисления ( enumперечислители) всегда имеют тип intв C, тогда как в C++ они являются отдельными типами и могут иметь размер, отличный от размера int. [ требуется обновление ]
  • В C++ constпеременная должна быть инициализирована; в C это не обязательно.
  • Компиляторы C++ запрещают goto или switch пересекать инициализацию, как в следующем коде C99:
    void fn ( void ) { goto flack ; int i = 1 ; flack :; }        
  • Хотя синтаксически это допустимо, a longjmp()приводит к неопределенному поведению в C++, если перепрыгиваемые стековые кадры включают объекты с нетривиальными деструкторами. [12] Реализация C++ свободна определять поведение таким образом, чтобы деструкторы вызывались. Однако это исключило бы некоторые применения, longjmp()которые в противном случае были бы допустимы, такие как реализация потоков или сопрограмм, переключающихся между отдельными стеками вызовов с longjmp()— при переходе из нижнего в верхний стек вызовов в глобальном адресном пространстве деструкторы вызывались бы для каждого объекта в нижнем стеке вызовов. В C такой проблемы нет.
  • C допускает несколько предварительных определений одной глобальной переменной в одной единице перевода , что является недопустимым нарушением ODR в C++.
    целое N ; целое N = 10 ;    
  • В языке C объявление нового типа с тем же именем, что и у существующего struct, unionили enumдопустимо, но в C++ это недопустимо, поскольку в C, struct, unionи enumтипы должны указываться как таковые всякий раз, когда на тип ссылаются, тогда как в C++ все объявления таких типов неявно содержат typedef .
    enum BOOL { ЛОЖЬ , ИСТИНА }; typedef int BOOL ;     
  • Объявления функций, не являющиеся прототипами (в стиле «K&R»), недопустимы в C++; они по-прежнему допустимы в C до C23, [13] [14], хотя они считались устаревшими с момента первоначальной стандартизации C в 1990 году. (Термин «устаревший» — это определенный термин в стандарте ISO C, означающий функцию, которая «может рассматриваться для отзыва в будущих редакциях» стандарта.) Аналогично, неявные объявления функций (использующие функции, которые не были объявлены) недопустимы в C++ и являются недопустимыми в C с 1999 года.
  • В C до C23 [15] объявление функции без параметров, например 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, и в прототипах функций, тогда как C++ этого не делает.enum

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

  • Комплексная арифметика с использованием float complexпримитивных double complexтипов данных и была добавлена ​​в стандарт C99 с помощью _Complexключевого слова и complexмакроса удобства. В C++ комплексная арифметика может быть выполнена с использованием класса комплексных чисел, но эти два метода несовместимы по коду. (Однако стандарты, начиная с C++11 , требуют двоичной совместимости.) [16]
  • Массивы переменной длины. Эта особенность приводит к возможному отсутствию времени компиляции оператора sizeof . [17]
    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 }                      
  • Последний член типа структуры C99 с более чем одним членом может быть гибким членом массива , который принимает синтаксическую форму массива с неопределенной длиной. Это служит цели, аналогичной массивам переменной длины, но VLA не могут появляться в определениях типов, и в отличие от VLA, гибкие члены массива не имеют определенного размера. ISO C++ не имеет такой возможности. Пример:
    struct X { int n , m ; char bytes []; }      
  • restrict Квалификатор типа, определенный в C99, не был включен в стандарт C++03, но большинство основных компиляторов, таких как GNU Compiler Collection , [18] 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++ будет использование digits = 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 использует отдельное ключевое слово. В C23 также поддерживается синтаксис атрибута. [19]

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

шаблон структуры { int new ; шаблон структуры * класс ; };       
является допустимым кодом C, но отклоняется компилятором C++, поскольку ключевые слова template, newи classзарезервированы.

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

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

  • Символьные литералы, такие как '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 различает два вида определений функций inline: обычные внешние определения (где externявно используется ) и встроенные определения. C++, с другой стороны, предоставляет только встроенные определения для встроенных функций. В C встроенное определение похоже на внутреннее (т. е. статическое) в том, что оно может сосуществовать в одной программе с одним внешним определением и любым количеством внутренних и встроенных определений той же функции в других единицах трансляции, все из которых могут отличаться. Это отдельное рассмотрение от связывания функции, но не независимое. Компиляторам C предоставляется право выбора между использованием встроенных и внешних определений одной и той же функции, когда оба они видны. Однако C++ требует, чтобы если функция с внешней связью объявлена inline​​в любой единице трансляции, то она должна быть объявлена ​​(и, следовательно, также определена) в каждой единице трансляции, где она используется, и чтобы все определения этой функции были идентичны, следуя ODR. Статические встроенные функции ведут себя одинаково в C и C++.
  • И в C99, и в 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.
  • В языке C то, является ли битовое поле типа знаковым или беззнаковым, определяется реализацией, 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++. В частности:

По этим причинам, для кода 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.

Ссылки

  1. ^ ab Stroustrup, Bjarne . "Обзор языка программирования C++ в The Handbook of Object Technology (редактор: Saba Zamir). CRC Press LLC, Boca Raton. 1999. ISBN 0-8493-3135-8" (PDF) . стр. 4. Архивировано (PDF) из оригинала 16 августа 2012 г. Получено 12 августа 2009 г.
  2. ^ Б.Страуструп. "C и C++: братья и сестры. Журнал пользователей C/C++. Июль 2002" (PDF) . Получено 17 марта 2019 г.
  3. ^ "Часто задаваемые вопросы Бьярна Страуструпа – Является ли C подмножеством C++?" . Получено 22 сентября 2019 г. .
  4. ^ Б. Страуструп. "C и C++: Дело о совместимости. Журнал пользователей C/C++. Август 2002" (PDF) . Архивировано (PDF) из оригинала 22 июля 2012 г. . Получено 18 августа 2013 г. .
  5. Обоснование международного стандарта — Языки программирования — C. Архивировано 6 июня 2016 г. на Wayback Machine , редакция 5.10 (апрель 2003 г.).
  6. ^ "Параметры диалекта C - Использование коллекции компиляторов GNU (GCC)". gnu.org . Архивировано из оригинала 26 марта 2014 г.
  7. ^ "N4659: Рабочий проект, стандарт языка программирования C++" (PDF) . §Приложение C.1. Архивировано (PDF) из оригинала 7 декабря 2017 г.(«Недопустимо переходить через объявление с явным или неявным инициализатором (за исключением всего блока, в который не вошел). … Благодаря этому простому правилу времени компиляции C++ гарантирует, что если инициализированная переменная находится в области видимости, то она наверняка была инициализирована».)
  8. ^ "N4659: Рабочий проект, стандарт языка программирования C++" (PDF) . §Приложение C.1. Архивировано (PDF) из оригинала 7 декабря 2017 г.
  9. ^ "Центр знаний IBM". ibm.com .
  10. ^ "FAQ > Casting malloc - Cprogramming.com". faq.cprogramming.com . Архивировано из оригинала 5 апреля 2007 г.
  11. ^ "Стандартные библиотечные функции, сохраняющие квалификаторы, v4" (PDF) .
  12. ^ "longjmp - Справочник по C++". www.cplusplus.com . Архивировано из оригинала 19 мая 2018 г.
  13. ^ «WG14 N2432: Удалить поддержку определений функций со списками идентификаторов» (PDF) .
  14. ^ "Проект стандарта ISO C 2011" (PDF) .
  15. ^ «WG14 N 2841: Нет деклараторов функций без прототипов».
  16. ^ "std::complex - cppreference.com". en.cppreference.com . Архивировано из оригинала 15 июля 2017 г.
  17. ^ "Несовместимости между ISO C и ISO C++". Архивировано из оригинала 9 апреля 2006 г.
  18. Ограниченные указатели. Архивировано 6 августа 2016 г. на Wayback Machine из раздела Использование коллекции компиляторов GNU (GCC).
  19. ^ "WG14-N2764: Атрибут noreturn" (PDF) . open-std.org . 21 июня 2021 г. Архивировано (PDF) из оригинала 25 декабря 2022 г.
  20. ^ "Центр знаний IBM". ibm.com .
  21. ^ "Центр знаний IBM". ibm.com .
  22. ^ "Oracle Documentation". Docs.sun.com. Архивировано из оригинала 3 апреля 2009 г. Получено 18 августа 2013 г.
  • Подробное сравнение, предложение за предложением, с точки зрения стандарта C89.
  • Несовместимости между ISO C и ISO C++, Дэвид Р. Триббл (август 2001 г.).
  • Руководство по миграции Oracle (Sun Microsystems) C++, раздел 3.11, Документация по компилятору Oracle/Sun по области действия связей.
  • Oracle: смешивание кода C и C++ в одной программе, обзор Стива Клэмеджа (председателя комитета ANSI C++).

Взято с "https://en.wikipedia.org/w/index.php?title=Совместимость_кода_C_и_C%2B%2B&oldid=1268287934#Связывание_кода_C_и_C++"