Участник:Joparino/Совместимость C и C++: различия между версиями

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Содержимое удалено Содержимое добавлено
До раздела «Constructs that behave differently in C and C++»
Строка 1: Строка 1:
[[Язык программирования|Языки программирования]] [[Си (язык программирования)|C]] и [[C++]] тесно связаны, но имеют существенные различия. C++ создавался как потомок [[ANSI C|достандартизированного]] C и был разработан так, чтобы быть в основном совместимым с исходным кодом C того времени.<ref name="overview">{{cite web | url=http://www.stroustrup.com/crc.pdf | title=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. | first=Bjarne | last=Stroustrup | author-link=Bjarne Stroustrup | page=4 | format=PDF | access-date=12 August 2009 | url-status=live | archive-url=https://web.archive.org/web/20120816122304/http://www.stroustrup.com/crc.pdf | archive-date=16 August 2012 | df=dmy-all }}</ref><ref>{{cite web |url=http://www.stroustrup.com/siblings_short.pdf |title=C and C++: Siblings. The C/C++ Users Journal. July 2002.|author=B.Stroustrup |access-date=17 March 2019}}</ref> В связи с этим, средства разработки для двух языков (такие, как [[Интегрированная среда разработки|интегрированные среды разработки]] и [[Компилятор|компиляторы]]) часто интегрировались в один продукт, при этом программист может указать C или C++ в качестве исходного языка.
[[Язык программирования|Языки программирования]] [[Си (язык программирования)|C]] и [[C++]] тесно связаны, но имеют существенные различия. C++ создавался как потомок [[ANSI C|достандартизированного]] C, по большей части совместимый с ним на тот момент на уровне исходного кода и компоновки<ref name="overview">{{cite web | url=http://www.stroustrup.com/crc.pdf | title=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. | first=Bjarne | last=Stroustrup | author-link=Bjarne Stroustrup | page=4 | format=PDF | access-date=12 August 2009 | url-status=live | archive-url=https://web.archive.org/web/20120816122304/http://www.stroustrup.com/crc.pdf | archive-date=16 August 2012}}</ref><ref>{{cite web |url=http://www.stroustrup.com/siblings_short.pdf |title=C and C++: Siblings. The C/C++ Users Journal. July 2002.|author=B.Stroustrup |access-date=17 March 2019}}</ref>. В связи с этим средства разработки для обоих языков (такие, как [[интегрированная среда разработки|среды разработки]] и [[компилятор]]ы) часто интегрируются в один продукт, при этом программист может выбрать C или C++ в качестве языка исходного кода.


Однако, C ''не является'' [[Подмножество|подмножеством]] C++,<ref name="subset">{{cite web |url=http://www.stroustrup.com/bs_faq.html#C-is-subset |title=Bjarne Stroustrup's FAQ&nbsp;– Is C a subset of C++? |access-date=22 Sep 2019}}</ref> поэтому нетривиальные программы на C не будут компилироваться на C++ без изменений. Также, C++ вводит множество возможностей, недоступных в C и на практике почти весь код, написанный на C++, не соответствует коду на C. Однако в этой статье основное внимание уделяется различиям, которые приводят к тому, что соответствующий код на C является неправильно написанным ({{lang-en|ill-formed}}) кодом C++ или соответствующим/хорошо написанным ({{lang-en|conforming/ill-formed}}) на обоих языках, но вести себя по-разному на C и C++.
Однако, C ''не является'' [[подмножество]]м C++<ref name="subset">{{cite web |url=http://www.stroustrup.com/bs_faq.html#C-is-subset |title=Bjarne Stroustrup's FAQ&nbsp;– Is C a subset of C++? |access-date=22 Sep 2019}}</ref>, поэтому нетривиальные программы на C не будут компилироваться на C++ без изменений. Также C++ вводит множество возможностей, недоступных в C, и на практике почти весь код, написанный на C++, не соответствует коду на C. Однако в этой статье основное внимание уделяется различиям, которые приводят к тому, что соответствующий код C является неправильно написанным ({{lang-en|ill-formed}}) кодом на C++ или соответствующим/хорошо написанным ({{lang-en|conforming/well-formed}}) на обоих языках, но может вести себя по-разному на C и C++.


[[Страуструп, Бьёрн|Бьёрн Страуструп]], создатель C++, предложил<ref>{{cite web |url=http://www.stroustrup.com/compat_short.pdf |title=C and C++: A Case for Compatibility. The C/C++ Users Journal. August 2002.|author=B. Stroustrup |access-date=18 August 2013 |url-status=live |archive-url=https://web.archive.org/web/20120722012742/http://www2.research.att.com/~bs/compat_short.pdf |archive-date=22 July 2012 |df=dmy-all }}</ref>, что несовместимость между C и C++ должна быть уменьшена насколько это возможно, чтобы обеспечить максимальную совместимость между двумя языками. Другие утверждают, что, поскольку C и C++ — это два разных языка, совместимость между ними полезна, но не жизненно важна; согласно им, усилия по уменьшению несовместимости не должны препятствовать усилиям по улучшению каждого языка в отдельности. Официальное обоснование стандарта C 1999 года ([[C99]]) "одобрить принцип сохранения наибольшего общего подмножества" между C и C++ "сохраняя при этом различие между ними и позволяя им развиваться отдельно", и заявило, что авторы были "довольны тем, что C++ стал большим и амбициозным языком."<ref>[http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf Rationale for International Standard—Programming Languages—C] {{webarchive|url=https://web.archive.org/web/20160606072228/http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf |date=6 June 2016 }}, revision 5.10 (April 2003).</ref>
[[Страуструп, Бьёрн|Бьёрн Страуструп]], создатель C++, предложил<ref>{{cite web |url=http://www.stroustrup.com/compat_short.pdf |title=C and C++: A Case for Compatibility. The C/C++ Users Journal. August 2002.|author=B. Stroustrup |access-date=18 August 2013 |url-status=live |archive-url=https://web.archive.org/web/20120722012742/http://www2.research.att.com/~bs/compat_short.pdf |archive-date=22 July 2012}}</ref> что несовместимость между C и C++ должна быть уменьшена насколько это возможно, чтобы обеспечить максимальное взамодейтвие между двумя языками. Другие утверждают, что, поскольку C и C++ — это два разных языка, совместимость между ними полезна, но не жизненно важна; согласно их мнению, усилия по уменьшению несовместимости не должны препятствовать попыткам улучшить каждый язык в отдельности. Официальное обоснование стандарта C 1999 года ([[C99]]) «поддерживает принцип сохранения наибольшего общего подмножества» между C и C++, «сохраняет при этом различия между ними и позволяет развиваться отдельно», там также утверждается, что авторы были «довольны тем, что C++ стал большим и амбициозным языком»<ref>[http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf Rationale for International Standard—Programming Languages—C] {{webarchive|url=https://web.archive.org/web/20160606072228/http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf |date=6 June 2016 }}, revision 5.10 (April 2003).</ref>.


Некоторые нововведения C99 не поддерживаются в текущем стандарте C++ или конфликтуют с особенностями C++, например массивы переменной длины, собственные [[Комплексный тип данных|комплексные типы данных]] и [[Квалификаторы типа|квалификатор типа]] <code>[[restrict]]</code>. С другой стороны, C99 уменьшил некоторые другие несовместимости по сравнению с C89, включив такие возможности C++, как <code>//</code> комментарии и смешанные объявления и код.<ref>{{cite web|url=https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html|title=C Dialect Options - Using the GNU Compiler Collection (GCC)|work=gnu.org|url-status=live|archive-url=https://web.archive.org/web/20140326130603/http://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html|archive-date=26 March 2014|df=dmy-all}}</ref>
Некоторые нововведения C99 не поддерживаются в текущем стандарте C++ или конфликтуют с отдельными возможностями C++, например, [[массив переменной длины|массивы переменной длины]], собственные [[комплексный тип данных|комплексные типы данных]] и [[квалификаторы типа|квалификатор типа]] <code>[[restrict]]</code>. С другой стороны, C99 уменьшил некоторые другие несовместимости по сравнению с C89, включив такие функции C++, как однострочные комментарии <code>//</code>, а также смешение объявлений и кода<ref>{{cite web|url=https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html|title=C Dialect Options - Using the GNU Compiler Collection (GCC)|work=gnu.org|url-status=live|archive-url=https://web.archive.org/web/20140326130603/http://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html|archive-date=26 March 2014}}</ref>.


==Конструкции, допустимые в C, но не в C++==
==Конструкции, допустимые в C, но не в C++==

C++ применяет более строгие правила типизации (никаких неявных нарушений системы статических типов<ref name="overview" />), и требования к инициализации (принудительное выполнение во время компиляции, чтобы переменные в области видимости не нарушали инициализацию)<ref>{{cite web|url=http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf|title=N4659: Working Draft, Standard for Programming Language C++|at=§Annex C.1|url-status=live|archive-url=https://web.archive.org/web/20171207092618/http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf|archive-date=7 December 2017|df=dmy-all}} ("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.")</ref> чем C, и поэтому некоторый допустимый код C недопустим в C++. Обоснование этого приведено в Приложении C.1 к стандарту ISO C++.<ref>{{cite web|url=http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf|title=N4659: Working Draft, Standard for Programming Language C++|at=§Annex C.1|url-status=live|archive-url=https://web.archive.org/web/20171207092618/http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf|archive-date=7 December 2017|df=dmy-all}}</ref>
C++ применяет более строгие правила типизации (никаких неявных нарушений системы статических типов<ref name="overview" />) и требования к инициализации (принудительная проверка во время компиляции, что у переменных в области видимости не нарушена инициализация, то есть невозможно вернуться к месту до объявления с явной или неявной инициализацией, если не считать блоки, в которые не управляющий поток не попадал)<ref>{{cite web|url=http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf|title=N4659: Working Draft, Standard for Programming Language C++|url-status=live|archive-url=https://web.archive.org/web/20171207092618/http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf|archive-date=7 December 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.")</ref>, и поэтому некоторый допустимый код C недопустим в C++. Обоснование этого приведено в Приложении C.1 к стандарту ISO C++<ref>{{cite web|url=http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf|title=N4659: Working Draft, Standard for Programming Language C++|url-status=live|archive-url=https://web.archive.org/web/20171207092618/http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf|archive-date=7 December 2017}}</ref>.


{{bulleted list
{{bulleted list
|1= Одно из часто встречающихся отличий заключается в том, что C более [[Сильная и слабая типизация|слабо типизирован]] в отношении указателей. В частности, C позволяет присваивать указателю <code>void*</code> любому типу указателя без приведения, в то время как C++ этого не позволяет; эта [[Идиома (программирование)|идиома]] часто встречается в коде C, использующем для выделения памяти <code>malloc</code>,<ref name="publib.boulder.ibm.com">{{cite web|url=http://publib.boulder.ibm.com/infocenter/macxhelp/v6v81/index.jsp?topic=%2Fcom.ibm.vacpp6m.doc%2Flanguage%2Fref%2Fclrc07cplr243.htm|title=IBM Knowledge Center|work=ibm.com}}</ref> или при передаче контекстных указателей на POSIX [[pthreads]] API, и другие фреймворки, включающие [[Callback (программирование)|обратные вызовы]]. Например, следующее допустимо в C, но не в C++:
|1= Одно из часто встречающихся отличий заключается в том, что C более [[сильная и слабая типизация|слабо типизирован]] в отношении указателей. В частности, C позволяет присваивать указатель <code>void*</code> любому типу указателя без приведения, в то время как C++ этого не позволяет; эта [[идиома (программирование)|идиома]] часто встречается в коде на C, использующем для выделения памяти <code>malloc</code><ref name="publib.boulder.ibm.com">{{cite web|url=http://publib.boulder.ibm.com/infocenter/macxhelp/v6v81/index.jsp?topic=%2Fcom.ibm.vacpp6m.doc%2Flanguage%2Fref%2Fclrc07cplr243.htm|title=IBM Knowledge Center|work=ibm.com}}</ref>, или при передаче контекстных указателей в [[pthreads]] (POSIX API) и другие фреймворки, использующие [[callback (программирование)|обратные вызовы]]. Например, следующее допустимо в C, но не в C++:


<syntaxhighlight lang="c">void *ptr;
<syntaxhighlight lang="c">void *ptr;
Строка 22: Строка 23:
<syntaxhighlight lang="c">int *j = malloc(5 * sizeof *j); /* Неявное преобразование из void* в int* */</syntaxhighlight>
<syntaxhighlight lang="c">int *j = malloc(5 * sizeof *j); /* Неявное преобразование из void* в int* */</syntaxhighlight>


Чтобы заставить код компилироваться как на C, так и на C++, необходимо использовать явное приведение типа следующим образом (с некоторыми оговорками на обоих языках<ref>{{cite web|url=http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?id=1043284351&answer=1047673478|title=FAQ > Casting malloc - Cprogramming.com|website=faq.cprogramming.com|url-status=live|archive-url=https://web.archive.org/web/20070405121807/http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?answer=1047673478&id=1043284351|archive-date=5 April 2007|df=dmy-all}}</ref><ref>{{cite web|url=http://www.learncpp.com/cpp-tutorial/4-4a-explicit-type-conversion-casting/|title=4.4a — Explicit type conversion (casting)|date=16 April 2015|url-status=live|archive-url=https://web.archive.org/web/20160925003229/http://www.learncpp.com/cpp-tutorial/4-4a-explicit-type-conversion-casting/|archive-date=25 September 2016|df=dmy-all}}</ref>):
Чтобы заставить код компилироваться как на C, так и на C++, необходимо использовать явное приведение типа следующим образом (с некоторыми предостережениями в отношении обоих языков<ref>{{cite web|url=http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?id=1043284351&answer=1047673478|title=FAQ > Casting malloc - Cprogramming.com|website=faq.cprogramming.com|url-status=live|archive-url=https://web.archive.org/web/20070405121807/http://faq.cprogramming.com/cgi-bin/smartfaq.cgi?answer=1047673478&id=1043284351|archive-date=5 April 2007}}</ref><ref>{{cite web|url=http://www.learncpp.com/cpp-tutorial/4-4a-explicit-type-conversion-casting/|title=4.4a — Explicit type conversion (casting)|date=16 April 2015|url-status=live|archive-url=https://web.archive.org/web/20160925003229/http://www.learncpp.com/cpp-tutorial/4-4a-explicit-type-conversion-casting/|archive-date=25 September 2016}}</ref>):


<syntaxhighlight lang="c++">
<syntaxhighlight lang="c++">
Строка 30: Строка 31:
</syntaxhighlight>
</syntaxhighlight>


|2= C++ имеет более сложные правила присваивания указателей, которые добавляют квалификаторы, поскольку он позволяет приводить <code>int **</code>
|2= C++ имеет более сложные правила присваивания указателей, которые добавляют квалификаторы, поскольку C++ позволяет приводить <code>int **</code> к <code>const int *const *</code>, но не позволяет небезопасное присваивание <code>const int **</code>, в то время как C не допускает ни того, ни другого (хотя компиляторы обычно выдают только предупреждение).
к <code>const int *const *</code>, но не небезопасное присваивание <code>const int **</code>, в то время как C не допускает ни того, ни другого (хотя компиляторы обычно выдают только предупреждение).


|3= C++ изменяет некоторые функции [[Стандартная библиотека языка Си|стандартной библиотеки языка С]], добавляя дополнительные перегруженные функции с [[Квалификаторы типа|квалификатором типа]] <code>[[Const (программирование)|const]]</code>, например <code>strchr</code> возвращает <code>char*</code> в C, в то время как C++ поступает так, как если бы были две перегруженные функции <code>const char *strchr(const char *)</code> и <code>char *strchr(char *)</code>.
|3= C++ изменяет некоторые функции [[стандартная библиотека языка Си|стандартной библиотеки языка Си]], добавляя дополнительные перегруженные функции с [[квалификаторы типа|квалификатором типа]] <code>[[const (программирование)|const]]</code>, например, <code>strchr</code> возвращает <code>char*</code> в C, в то время как C++ поступает так, как если бы существовали две перегруженные функции <code>const char *strchr(const char *)</code> и <code>char *strchr(char *)</code>.


|4= C++ также более строг в преобразованиях в перечисления: целые числа не могут быть неявно преобразованы в перечисления, как в C. Кроме того, [[Перечисляемый тип#C++
|4= C++ также более строг в преобразованиях в перечисления: целые числа не могут быть неявно преобразованы в перечисления, как в C. Кроме того, [[перечисляемый тип#C++|константные перечисления]] (<code>enum</code> enumerators) всегда имеют тип <code>int</code> в C, тогда как в C++ они являются различными типами и могут иметь размер, отличный от размера <code>int</code>.
|константные перечисления]] (<code>enum</code>) всегда имеют тип <code>int</code> в C, тогда как в C++ они являются различными типами и могут иметь размер, отличный от размера <code>int</code>.


|5= В C++ переменная <code>const</code> должна быть инициализирована; в C это необязательно.
|5= В C++ <code>const</code>-переменная должна быть инициализирована; в C это необязательно.


|6= Компиляторы C++ запрещают goto или switch пересекать инициализацию, как в следующем коде C99:
|6= Компиляторы C++ запрещают <code>goto</code> или <code>switch</code> пересекать инициализацию, как в следующем коде на C99:
<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
void fn(void)
void fn(void)
Строка 51: Строка 50:
</syntaxhighlight>
</syntaxhighlight>


|7= Несмотря на синтаксическую корректность, функция [[longjmp|<code>longjmp()</code>]] приводит к неопределенному поведению в C++, если [[Стек вызовов|фреймы стека]] с перепрыгиванием включают объекты с нетривиальными деструкторами.<ref>{{cite web|url=http://www.cplusplus.com/reference/csetjmp/longjmp/|title=longjmp - C++ Reference|website=www.cplusplus.com|url-status=live|archive-url=https://web.archive.org/web/20180519163512/http://www.cplusplus.com/reference/csetjmp/longjmp/|archive-date=19 May 2018|df=dmy-all}}</ref> Реализация C++ может свободно определять поведение таким образом, чтобы вызывались деструкторы. Однако это мешает некоторым видам использования <code>longjmp()</code>, которые в противном случае были бы допустимы, такие как [[Поток выполнения|потоки]] или [[Сопрограмма|сопрограммы]] переключающиеся между отдельными стеками вызовов с помощью <code>longjmp()</code> — при переходе из нижнего стека вызовов в верхний в глобальном адресном пространстве деструкторы будут вызываться для каждого объекта в нижнем стеке вызовов. В C такой проблемы не существует.
|7= Несмотря на синтаксическую корректность, функция [[longjmp|<code>longjmp()</code>]] приводит к неопределённому поведению в C++, если пропущенные ({{lang-en|jumped-over}}) [[стек вызовов|фреймы стека]] содержат объекты с нетривиальными деструкторами<ref>{{cite web|url=http://www.cplusplus.com/reference/csetjmp/longjmp/|title=longjmp - C++ Reference|website=www.cplusplus.com|url-status=live|archive-url=https://web.archive.org/web/20180519163512/http://www.cplusplus.com/reference/csetjmp/longjmp/|archive-date=19 May 2018}}</ref>. Имплементация C++ может произвольно определять поведение таким образом, чтобы деструкторы вызывались. Однако это исключает некоторые варианты использования <code>longjmp()</code>, которые в противном случае были бы допустимы, например, имплементация [[поток выполнения|потоков]] или [[сопрограмма|сопрограмм]], переключающихся между отдельными стеками вызовов с помощью <code>longjmp()</code> — при переходе из нижнего стека вызовов в верхний в глобальном адресном пространстве деструкторы вызывались бы для каждого объекта в нижнем стеке вызовов. В C такой проблемы не существует.


|8= C допускает несколько предварительных определений одной глобальной переменной в одной [[Единица трансляции|единице трансляции]], что недопустимо в C++, так как это нарушение [[Правило одного определения|правила одного определения]] ({{lang-en|One Definition Rule, ODR}}).
|8= C допускает несколько предварительных определений одной глобальной переменной в одной [[единица трансляции|единице трансляции]], что недопустимо в C++, так как это нарушение [[правило одного определения|правила одного определения]] ({{lang-en|One Definition Rule, ODR}}).
<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
int N;
int N;
Строка 59: Строка 58:
</syntaxhighlight>
</syntaxhighlight>


|9= В C допустимо объявление нового типа с тем же именем, что и у <code>struct</code>, <code>union</code> или <code>enum</code>, но это недопустимо в C++, потому что в C типы, <code>struct</code>, <code>union</code>, и <code>enum</code> должны указываться всякий раз, когда на тип ссылаются, тогда как в C++ все объявления таких типов неявно содержат <code>typedef</code>.
|9= В C допустимо объявление нового типа с тем же именем, что и у <code>struct</code>, <code>union</code> или <code>enum</code>, но это недопустимо в C++, потому что в C типы <code>struct</code>, <code>union</code> и <code>enum</code> должны указываться всякий раз, когда на этот тип ссылаются, тогда как в C++ все объявления таких типов неявно содержат [[typedef]].
<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
enum BOOL {FALSE, TRUE};
enum BOOL {FALSE, TRUE};
Строка 65: Строка 64:
</syntaxhighlight>
</syntaxhighlight>


|10= Объявления функций, не являющихся прототипами[[Отступ (программирование)#Стиль «K&R»|стиле «K&R»]]), недопустимы в C++; они по-прежнему действительны в C,<ref>{{cite web|url=http://www.open-std.org/JTC1/sc22/wg14/www/docs/n1570.pdf|title=2011 ISO C draft standard}}</ref> хотя они были признаны устаревшими с момента первоначальной стандартизации C в 1990 году. (термин «устаревший» — это определенный термин в стандарте ISO C, означающий функцию, которая «может быть рассмотрена для удаления в будущих версиях» стандарта.) Аналогично, неявные объявления функций (с использованием функций, которые не были объявлены) не допускаются в C++ и являются недопустимыми в C с 1999 года.
|10= Объявления функций без прототипов (в стиле «K&R») недопустимы в C++; они по-прежнему действительны в C<ref>{{cite web|url=http://www.open-std.org/JTC1/sc22/wg14/www/docs/n1570.pdf|title=2011 ISO C draft standard}}</ref>, хотя были признаны устаревшими с момента первой стандартизации C в 1990 году. «Устаревший» ({{lang-en|obsolescent}}) — это термин, которому даётся определение в стандарте ISO C, он означает языковую возможность, которая «может быть быть удалена в будущих версиях» стандарта. Аналогично, неявные объявления функций (использование функций, которые не были объявлены) не допускаются в C++ и являются недопустимыми в C с 1999 года.


|11= В C прототип функции без аргументов, например <code>int foo();</code>, подразумевает, что аргументы не указаны. Следовательно, законно вызывать такую функцию с одним или несколькими [[Параметр (программирование)|аргументами]], например <code>foo(42, "hello world")</code>. Напротив, в C++ прототип функции без аргументов означает, что функция не принимает аргументов, и вызов такой функции с аргументами является некорректным. В C правильный способ объявить функцию, которая не принимает аргументов, - это использовать 'void', как в <code>int foo(void);</code>, это также допустимо в C++. Пустые прототипы функций являются устаревшей функцией в C99 (как и в C89).
|11= В C прототип функции без параметров, например <code>int foo();</code>, подразумевает, что параметры не указаны. Следовательно, допустимо вызывать такую функцию с одним или несколькими [[параметр (программирование)|параметрами]], например <code>foo(42, "hello world")</code>. Напротив, в C++ прототип функции без аргументов означает, что функция не принимает аргументов, и вызов такой функции с аргументами является некорректным. В C правильный способ объявить функцию, которая не принимает аргументов, это использовать 'void', как в <code>int foo(void);</code>, это также допустимо в C++. Пустые прототипы функций являются устаревшей ({{lang-en|deprecated}}) возможностью в C99 (как и в C89).


|12= Как на C, так и на C++, можно определить типы вложенных <code>[[Структура (язык Си)|struct]]</code>, но область действия интерпретируется по-разному: в C++ вложенная <code>struct</code> определяется только в пределах области/пространства имен внешней <code>struct</code>, тогда как в C внутренняя структура также определяется вне внешней структуры.
|12= Как на C, так и на C++ можно определить вложенные типы <code>[[структура (язык Си)|struct]]</code>, но область действия интерпретируется по-разному: в C++ вложенный тип <code>struct</code> определяется только в пределах области видимости/пространства имён внешнего типа <code>struct</code>, тогда как в C внутренняя структура также определяется вне внешней структуры.


|13= C позволяет объявлять типы <code>[[Структура (язык Си)|struct]]</code>, <code>[[Объединение (структура данных)|union]]</code>, и <code>[[Перечисляемый тип#C и языки с C-подобным синтаксисом|enum]]</code> в прототипах функций, в то время как C++ этого не позволяет.
|13= C позволяет объявлять типы <code>[[структура (язык Си)|struct]]</code>, <code>[[объединение (структура данных)|union]]</code> и <code>[[перечисляемый тип#C и языки с C-подобным синтаксисом|enum]]</code> в прототипах функций, в то время как C++ этого не делает.
}}
}}


[[C99]] и [[C11]] добавили в C несколько дополнительных возможностей, которые не были включены в стандартный C++, таких как комплексные числа, массивы переменной длины (обратите внимание, что комплексные числа и массивы переменной длины обозначены как необязательные расширения в C11), гибкий элемент массива, ключевое слово [[restrict]], квалификаторы параметров массива, составные литералы ({{lang-en|compound literals}}), и назначенные инициализаторы ({{lang-en|designated initializers}}).
[[C99]] и [[C11]] добавили в C несколько дополнительных возможностей, которые не были включены в стандартный C++, таких как комплексные числа, массивы переменной длины (обратите внимание, что комплексные числа и массивы переменной длины обозначены как необязательные расширения в C11), {{нп5|гибкий элемент массива|гибкий элемент массива|en|flexible array member}}, ключевое слово [[restrict]], квалификаторы параметров массива, составные литералы ({{lang-en|compound literals}}) и {{нп5|назначенные инициализаторы|назначенные инициализаторы|en|designated initializers}}.


{{bulleted list
{{bulleted list
|1= [[Комплексное число|Комплексная арифметика]] с использованием примитивных типов данных <code>float complex</code> и <code>double complex</code> были добавлены в стандарте [[C99]] с помощью ключевого слова <code>_Complex</code> и удобного макроса <code>complex</code>. В C++ комплексная арифметика может быть выполнена с использованием класса комплексных чисел, но эти два метода несовместимы с кодом. (Однако стандарты, начиная с [[C++11]], требуют двоичной совместимости.)<ref>{{cite web|url=http://en.cppreference.com/ruwiki/w/cpp/numeric/complex|title=std::complex - cppreference.com|website=en.cppreference.com|url-status=live|archive-url=https://web.archive.org/web/20170715125445/http://en.cppreference.com/ruwiki/w/cpp/numeric/complex|archive-date=15 July 2017|df=dmy-all}}</ref>
|1= [[комплексное число|Комплексная арифметика]] с использованием примитивных типов данных <code>float complex</code> и <code>double complex</code> были добавлены в стандарт [[C99]] с помощью ключевого слова <code>_Complex</code> и макроса <code>complex</code> для удобства. В C++ арифметические действия с комплексными числами может быть выполнена с использованием класса комплексных чисел, но эти два метода несовместимы на уровне кода. (Однако стандарты, начиная с [[C++11]], требуют бинарной совместимости.)<ref>{{cite web|url=http://en.cppreference.com/ruwiki/w/cpp/numeric/complex|title=std::complex - cppreference.com|website=en.cppreference.com|url-status=live|archive-url=https://web.archive.org/web/20170715125445/http://en.cppreference.com/ruwiki/w/cpp/numeric/complex|archive-date=15 July 2017}}</ref>


|2= Массивы переменной длины {{lang-en|Variable length arrays, VLA}}. Эта особенность приводит к возможному отсутствию оператора sizeof во время компиляции.<ref>{{cite web|title=Incompatibilities Between ISO C and ISO C++|url=http://david.tribble.com/text/cdiffs.htm#C90-vs-CPP98|url-status=live|archive-url=https://web.archive.org/web/20060409075755/http://david.tribble.com/text/cdiffs.htm#C90-vs-CPP98|archive-date=9 April 2006|df=dmy-all}}</ref>
|2= Массивы переменной длины. Эта особенность приводит к возможному вызову оператора <><code>sizeof</code> не во время компиляции<ref>{{cite web|title=Incompatibilities Between ISO C and ISO C++|url=http://david.tribble.com/text/cdiffs.htm#C90-vs-CPP98|url-status=live|archive-url=https://web.archive.org/web/20060409075755/http://david.tribble.com/text/cdiffs.htm#C90-vs-CPP98|archive-date=9 April 2006}}</ref>.
<syntaxhighlight lang="C">
<syntaxhighlight lang="C">
void foo(size_t x, int a[*]); // объявление VLA
void foo(size_t x, int a[*]); // Объявление VLA
void foo(size_t x, int a[x])
void foo(size_t x, int a[x])
{
{
printf("%zu\n", sizeof a); // то же, что и sizeof(int*)
printf("%zu\n", sizeof a); // То же, что и sizeof(int*)
char s[x * 2];
char s[x * 2];
printf("%zu\n", sizeof s); // будет выведено print x*2
printf("%zu\n", sizeof s); // Будет выведено print x*2
}
}
</syntaxhighlight>
</syntaxhighlight>
<!-- * C99 allows declaration of function macros with empty arguments. For example macro <code>#define ADD(A,B) (A+B)</code> applied to <code>ADD(4,)</code> will be evaluated to <code>(4+)</code> -->


|3= Последний элемент структурного типа в стандарте C99 с более чем одним элементом может быть гибким элементом массива, который принимает синтаксическую форму массива с неопределенной длиной. Это служит цели, аналогичной массивам переменной длины, но массивы переменной длины не могут отображаться в определениях типов, и, в отличие от массивов переменной длины, элементы гибкого массива не имеют определенного размера. ISO C++ не имеет такой особенности. Пример:
|3= Последний элемент структурного типа в стандарте C99 с более чем одним элементом может быть {{нп5|гибкий элемент массива|гибким элементом массива|en|flexible array member}}, который принимает синтаксическую форму массива с неопределённой длиной. Это служит цели, аналогичной таковой у массивов переменной длины, но очень большие массивы ({{lang-en|very large arrays, VLAs}}) не могут находиться в определениях типов, и, в отличие от очень больших массивов, элементы гибкого массива не имеют определённого размера. ISO C++ не имеет такой особенности. Пример:
<syntaxhighlight lang="C">
<syntaxhighlight lang="C">
struct X
struct X
Строка 99: Строка 99:
</syntaxhighlight>
</syntaxhighlight>


|4= [[Квалификаторы типа|Квалификатор типа]] <code>[[restrict]]</code>, определенный в C99, не был включен в стандарт C++03, но большинство основных компиляторов, таких как [[GNU Compiler Collection]],<ref>[https://gcc.gnu.org/onlinedocs/gcc/Restricted-Pointers.html#Restricted-Pointers Restricted Pointers] {{webarchive|url=https://web.archive.org/web/20160806092010/http://gcc.gnu.org/onlinedocs/gcc/Restricted-Pointers.html |date=6 August 2016 }} from ''Using the GNU Compiler Collection (GCC)''</ref> [[Microsoft Visual C++]], и [[Intel C++ Compiler]] , предоставляют аналогичную функциональность в качестве расширения.
|4= [[Квалификаторы типа|Квалификатор типа]] <code>[[restrict]]</code>, определённый в C99, не был включён в стандарт C++03, но большинство основных компиляторов, таких как [[GNU Compiler Collection|GCC]]<ref>[https://gcc.gnu.org/onlinedocs/gcc/Restricted-Pointers.html#Restricted-Pointers Restricted Pointers] {{webarchive|url=https://web.archive.org/web/20160806092010/http://gcc.gnu.org/onlinedocs/gcc/Restricted-Pointers.html |date=6 August 2016 }} from ''Using the GNU Compiler Collection (GCC)''</ref>, [[Microsoft Visual C++]] и [[Intel C++ Compiler]] предоставляют аналогичную функциональность в качестве расширения.


|5= Квалификаторы параметров массива в функциях поддерживаются в C, но не в C++
|5= Квалификаторы параметров массива в функциях поддерживаются в C, но не в C++.
<syntaxhighlight lang="C">
<syntaxhighlight lang="C">
int foo(int a[const]); // аналогично int *const a
int foo(int a[const]); // аналогично int *const a
int bar(char s[static 5]); // отметим, что s имеет длину не менее 5 символов
int bar(char s[static 5]); // отмечает, что s имеет длину не менее 5 символов
</syntaxhighlight>
</syntaxhighlight>


|6= Функциональность составных литералов в C обобщается как на встроенные, так и на пользовательские типы синтаксисом инициализации списка C++11, хотя и с некоторыми синтаксическими и семантическими различиями.
|6= Функциональность составных литералов в C обобщается как на встроенные, так и на пользовательские типы с помощью синтаксиса списочной инициализаци в C++11, хотя и с некоторыми синтаксическими и семантическими различиями.
<syntaxhighlight lang="C">
<syntaxhighlight lang="C">
struct X a = (struct X){4, 6}; // Аналогичным в C++ было бы X{4, 6}. Синтаксическая форма C, используемая в C99, поддерживается в качестве расширения в компиляторах GCC и Clang C++.
struct X a = (struct X){4, 6}; // Аналогичным в C++ было бы X{4, 6}. Синтаксическая форма C, используемая в C99, поддерживается в качестве расширения в компиляторах GCC и Clang.
foo(&(struct X){4, 6}); // Объект выделяется в стеке, и его адрес может быть передан функции. Это не поддерживается в 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) {}
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) {}
</syntaxhighlight>
</syntaxhighlight>


|7= Назначенные инициализаторы для массивов допустимы только в C:
|7= Назначенные инициализаторы для массивов допустимы только в C:
<syntaxhighlight lang="C">
<syntaxhighlight lang="C">
char s[20] = { [0] = 'a', [8] = 'g' }; // допустимо в C, но не в C++
char s[20] = { [0] = 'a', [8] = 'g' }; // Допустимо в C, но не в C++
</syntaxhighlight>
</syntaxhighlight>


|8= Функции, которые не возвращают что-либо, могут быть отмечены с помощью атрибута noreturn в C++, тогда как C использует ключевое слово distinct.
|8= Функции, которые не возвращают значение, могут быть отмечены с помощью атрибута noreturn в C++, тогда как C использует другое ключевое слово.
}}
}}


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


<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
Строка 133: Строка 133:
</syntaxhighlight>
</syntaxhighlight>


:является допустимым кодом C, но отклоняется компилятором C++, поскольку ключевые слова <code>template</code>, <code>new</code> и<code>class</code> зарезервированы.
:является допустимым кодом на C, но отклоняется компилятором C++, поскольку ключевые слова <code>template</code>, <code>new</code> и<code>class</code> зарезервированы.


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

C++, однако, требует, чтобы если функция с внешней связью была объявлена <code>inline</code> в любой единице перевода, то она должна быть объявлена (и, следовательно, также определена) в каждой единице перевода, где она используется, и чтобы все определения этой функции были идентичны, следуя УСО.


* Символьные литералы ({{lang-en|Character literal}}), такие как <code>'a'</code>, имеют тип <code>int</code> в C и тип <code>char</code> в C++, что означает, что <code>sizeof 'a'</code> обычно дает разные результаты на двух языках: в C++ это будет <code>1</code>, в то время как в C это будет <code>sizeof(int)</code>. Как еще одно следствие этого различия в типах, в C <code>'a'</code> всегда будет выражением со знаком, независимо от того, является ли char знаковым или беззнаковым, , тогда как для C++ это зависит от реализации компилятора.
* Символьные литералы ({{lang-en|Character literal}}), такие как <code>'a'</code>, имеют тип <code>int</code> в C и тип <code>char</code> в C++, что означает, что <code>sizeof 'a'</code> обычно дает разные результаты на двух языках: в C++ это будет <code>1</code>, в то время как в C это будет <code>sizeof(int)</code>. Как еще одно следствие этого различия в типах, в C <code>'a'</code> всегда будет выражением со знаком, независимо от того, является ли char знаковым или беззнаковым, , тогда как для C++ это зависит от реализации компилятора.
Строка 186: Строка 188:
</syntaxhighlight>
</syntaxhighlight>


Различия между соглашениями о связывании и вызовах C и C++ также могут иметь тонкие последствия для кода, использующего указатели на функции. Некоторые компиляторы выдадут нерабочий код, если указатель функции, объявленный <code>extern "C"</code>, указывает на функцию C++, которая не объявлена <code>extern "C"</code>.<ref>{{cite web |url=http://docs.sun.com/source/819-3689/Ch3.Std.html#pgfId-18503 |title=Oracle Documentation |publisher=Docs.sun.com |access-date=18 August 2013 |url-status=live |archive-url=https://web.archive.org/web/20090403134627/http://docs.sun.com/source/819-3689/Ch3.Std.html#pgfId-18503 |archive-date=3 April 2009 |df=dmy-all }}</ref>
Различия между соглашениями о связывании и вызовах C и C++ также могут иметь тонкие последствия для кода, использующего указатели на функции. Некоторые компиляторы выдадут нерабочий код, если указатель функции, объявленный <code>extern "C"</code>, указывает на функцию C++, которая не объявлена <code>extern "C"</code>.<ref>{{cite web |url=http://docs.sun.com/source/819-3689/Ch3.Std.html#pgfId-18503 |title=Oracle Documentation |publisher=Docs.sun.com |access-date=18 August 2013 |url-status=live |archive-url=https://web.archive.org/web/20090403134627/http://docs.sun.com/source/819-3689/Ch3.Std.html#pgfId-18503 |archive-date=3 April 2009}}</ref>


Например, следующий код:
Например, следующий код:
Строка 212: Строка 214:


==Внешние ссылки==
==Внешние ссылки==
{{wikibooks|C++ Programming|Programming Languages/Comparisons/C}}
* [http://www.coding-guidelines.com/cbook/c90c++.pdf Detailed comparison], sentence by sentence, from a C89 Standard perspective.
* [http://www.coding-guidelines.com/cbook/c90c++.pdf Detailed comparison], sentence by sentence, from a C89 Standard perspective.
* [http://david.tribble.com/text/cdiffs.htm Incompatibilities Between ISO C and ISO C++], David R. Tribble (August 2001).
* [http://david.tribble.com/text/cdiffs.htm Incompatibilities Between ISO C and ISO C++], David R. Tribble (August 2001).

Версия от 18:26, 27 июля 2022

Языки программирования C и C++ тесно связаны, но имеют существенные различия. C++ создавался как потомок достандартизированного C, по большей части совместимый с ним на тот момент на уровне исходного кода и компоновки[1][2]. В связи с этим средства разработки для обоих языков (такие, как среды разработки и компиляторы) часто интегрируются в один продукт, при этом программист может выбрать C или C++ в качестве языка исходного кода.

Однако, C не является подмножеством C++[3], поэтому нетривиальные программы на C не будут компилироваться на C++ без изменений. Также C++ вводит множество возможностей, недоступных в C, и на практике почти весь код, написанный на C++, не соответствует коду на C. Однако в этой статье основное внимание уделяется различиям, которые приводят к тому, что соответствующий код C является неправильно написанным (англ. ill-formed) кодом на C++ или соответствующим/хорошо написанным (англ. conforming/well-formed) на обоих языках, но может вести себя по-разному на 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.1 к стандарту ISO C++[8].

  • Одно из часто встречающихся отличий заключается в том, что C более слабо типизирован в отношении указателей. В частности, C позволяет присваивать указатель void* любому типу указателя без приведения, в то время как C++ этого не позволяет; эта идиома часто встречается в коде на C, использующем для выделения памяти malloc[9], или при передаче контекстных указателей в pthreads (POSIX 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++ имеет более сложные правила присваивания указателей, которые добавляют квалификаторы, поскольку C++ позволяет приводить int ** к const int *const *, но не позволяет небезопасное присваивание const int **, в то время как C не допускает ни того, ни другого (хотя компиляторы обычно выдают только предупреждение).
  • C++ изменяет некоторые функции стандартной библиотеки языка Си, добавляя дополнительные перегруженные функции с квалификатором типа const, например, strchr возвращает char* в C, в то время как C++ поступает так, как если бы существовали две перегруженные функции const char *strchr(const char *) и char *strchr(char *).
  • 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++, если пропущенные (англ. jumped-over) фреймы стека содержат объекты с нетривиальными деструкторами[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 году. «Устаревший» (англ. obsolescent) — это термин, которому даётся определение в стандарте ISO C, он означает языковую возможность, которая «может быть быть удалена в будущих версиях» стандарта. Аналогично, неявные объявления функций (использование функций, которые не были объявлены) не допускаются в C++ и являются недопустимыми в C с 1999 года.
  • В C прототип функции без параметров, например int foo();, подразумевает, что параметры не указаны. Следовательно, допустимо вызывать такую функцию с одним или несколькими параметрами, например foo(42, "hello world"). Напротив, в C++ прототип функции без аргументов означает, что функция не принимает аргументов, и вызов такой функции с аргументами является некорректным. В C правильный способ объявить функцию, которая не принимает аргументов, — это использовать 'void', как в int foo(void);, это также допустимо в C++. Пустые прототипы функций являются устаревшей (англ. deprecated) возможностью в C99 (как и в C89).
  • Как на C, так и на C++ можно определить вложенные типы struct, но область действия интерпретируется по-разному: в C++ вложенный тип struct определяется только в пределах области видимости/пространства имён внешнего типа struct, тогда как в C внутренняя структура также определяется вне внешней структуры.
  • C позволяет объявлять типы struct, union и enum в прототипах функций, в то время как C++ этого не делает.

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

  • Комплексная арифметика с использованием примитивных типов данных 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 с более чем одним элементом может быть гибким элементом массива[англ.], который принимает синтаксическую форму массива с неопределённой длиной. Это служит цели, аналогичной таковой у массивов переменной длины, но очень большие массивы (англ. very large arrays, VLAs) не могут находиться в определениях типов, и, в отличие от очень больших массивов, элементы гибкого массива не имеют определённого размера. ISO C++ не имеет такой особенности. Пример:
    struct X
    {
        int n, m;
        char bytes[];
    }
    
  • Квалификатор типа restrict, определённый в C99, не был включён в стандарт C++03, но большинство основных компиляторов, таких как GCC[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.
    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 использует другое ключевое слово.

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++.

Связывание кода C и C++

В то время как C и C++ поддерживают высокую степень совместимости исходных текстов, объектные файлы, создаваемые их соответствующими компиляторами, могут иметь важные различия, которые проявляются при смешивании кода C и C++. Особенно:

  • Компиляторы C не называют искаженные символы (англ. name mangle symbols) так, как это делают компиляторы C++.[17]
  • В зависимости от компилятора и архитектуры, также может быть так, что соглашения о вызовах различаются между двумя языками.

По этим причинам, чтобы код C++ вызывал функцию C foo(), код C++ должен создавать прототип foo() с помощью extern "C". Аналогично, чтобы код C вызывал функцию C++ bar(), код C++ для bar() должен быть объявлен с extern "C".

Обычная практика для заголовочных файлов для поддержания совместимости как с C, так и С++ заключается в том, чтобы сделать его объявление extern "C" для области видимости заголовка:[18]

/* Заголовочный файл 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".[19]

Например, следующий код:

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().

Ссылки

  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 года.

Внешние ссылки

Категория:Язык программирования Си Категория:C++