Участник:Joparino/Совместимость C и C++

Материал из Википедии — свободной энциклопедии
Это старая версия этой страницы, сохранённая Joparino (обсуждение | вклад) в 20:47, 26 июля 2022 (боже как же я устал этот английский язык). Она может серьёзно отличаться от текущей версии.
Перейти к навигации Перейти к поиску

Языки программирования 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 допускает несколько предварительных определений одной глобальной переменной в одной единице перевода, что недопустимо как нарушение ODR в 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 зарезервированы.

Constructs that behave differently in C and C++

There are a few syntactic constructs that are valid in both C and C++ but produce different results in the two languages.

  • Character literals such as 'a' are of type int in C and of type char in C++, which means that sizeof 'a' will generally give different results in the two languages: in C++, it will be 1, while in C it will be sizeof(int). As another consequence of this type difference, in C, 'a' will always be a signed expression, regardless of whether or not char is a signed or unsigned type, whereas for C++ this is compiler implementation specific.
  • C++ assigns internal linkage to namespace-scoped const variables unless they are explicitly declared extern, unlike C in which extern is the default for all file-scoped entities. Note that in practice this does not lead to silent semantic changes between identical C and C++ code but instead will lead to a compile-time or linkage error.
  • In C use of inline functions requires manually adding a prototype declaration of the function using the extern keyword in exactly one translation unit to ensure a non-inlined version is linked in, whereas C++ handles this automatically. In more detail, C distinguishes two kinds of definitions of inline functions: ordinary external definitions (where extern is explicitly used) and inline definitions. C++, on the other hand, provides only inline definitions for inline functions. In C, an inline definition is similar to an internal (i.e. static) one, in that it can coexist in the same program with one external definition and any number of internal and inline definitions of the same function in other translation units, all of which can differ. This is a separate consideration from the linkage of the function, but not an independent one. C compilers are afforded the discretion to choose between using inline and external definitions of the same function when both are visible. C++, however, requires that if a function with external linkage is declared inline in any translation unit then it must be so declared (and therefore also defined) in every translation unit where it is used, and that all the definitions of that function be identical, following the ODR. Note that static inline functions behave identically in C and C++.
  • Both C99 and C++ have a boolean type bool with constants true and false, but they are defined differently. In C++, bool is a built-in type and a reserved keyword. In C99, a new keyword, _Bool, is introduced as the new boolean type. The header stdbool.h provides macros bool, true and false that are defined as _Bool, 1 and 0, respectively. Therefore, true and false have type int in C.

Several of the other differences from the previous section can also be exploited to create code that compiles in both languages but behaves differently. For example, the following function will return different values in C and C++:

extern int T;

int size(void)
{
    struct T {  int i;  int j;  };
    
    return sizeof(T);
    /* C:   return sizeof(int)
     * C++: return sizeof(struct T)
     */
}

This is due to C requiring struct in front of structure tags (and so sizeof(T) refers to the variable), but C++ allowing it to be omitted (and so sizeof(T) refers to the implicit typedef). Beware that the outcome is different when the extern declaration is placed inside the function: then the presence of an identifier with same name in the function scope inhibits the implicit typedef to take effect for C++, and the outcome for C and C++ would be the same. Observe also that the ambiguity in the example above is due to the use of the parenthesis with the sizeof operator. Using sizeof T would expect T to be an expression and not a type, and thus the example would not compile with 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. 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.
  3. Bjarne Stroustrup's FAQ – Is C a subset of C++? Дата обращения: 22 сентября 2019.
  4. B. Stroustrup. C and C++: A Case for Compatibility. The C/C++ Users Journal. August 2002. Дата обращения: 18 августа 2013. Архивировано 22 июля 2012 года.
  5. Rationale for International Standard—Programming Languages—C Архивировано 6 июня 2016 года., revision 5.10 (April 2003).
  6. C Dialect Options - Using the GNU Compiler Collection (GCC). gnu.org. Архивировано 26 марта 2014 года.
  7. 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.")
  8. N4659: Working Draft, Standard for Programming Language C++. Архивировано 7 декабря 2017 года.
  9. IBM Knowledge Center. ibm.com.
  10. FAQ > Casting malloc - Cprogramming.com. faq.cprogramming.com. Архивировано 5 апреля 2007 года.
  11. 4.4a — Explicit type conversion (casting) (16 апреля 2015). Архивировано 25 сентября 2016 года.
  12. longjmp - C++ Reference. www.cplusplus.com. Архивировано 19 мая 2018 года.
  13. 2011 ISO C draft standard.
  14. std::complex - cppreference.com. en.cppreference.com. Архивировано 15 июля 2017 года.
  15. Incompatibilities Between ISO C and ISO C++. Архивировано 9 апреля 2006 года.
  16. Restricted Pointers Архивировано 6 августа 2016 года. from Using the GNU Compiler Collection (GCC)
  17. IBM Knowledge Center. ibm.com.
  18. IBM Knowledge Center. ibm.com.
  19. Oracle Documentation. Docs.sun.com. Дата обращения: 18 августа 2013. Архивировано 3 апреля 2009 года.

Шаблон:CProLang Шаблон:C++ programming language

Шаблон:Use dmy dates