Страницы

Поиск по вопросам

воскресенье, 24 ноября 2019 г.

Какие элементы С являются неподдерживаемыми в С++?


Какие элементы языка С являются неподдерживаемыми в С++? Какой код на С не буде
принят компилятором С++? Особенно интересует поведение g++.
    


Ответы

Ответ 1



Развитие C++ и C разделилось. У C++ выходит свой стандарт, у C - свой. C++ поддерживае все возможности C89, но не поддерживает более новые - C99 и C11. Конечно, это зависи от реализации компилятора, но в стандарт C++11 по-прежнему входят возможности только C89. Правда, в него добавили long long, который появился ещё в C99, и ещё пару возможностей. Сам я новых возможностей C не знаю, так как использую C++, но, судя по википедии, в C99 есть следующие возможности, отсутствующие в C++: Массивы переменной длины Типовые математические функции (tgmath.h) Проектируемые инициализаторы Составные константы Смягчение (restrict) ограничений для более агрессивной оптимизации кода В C11: Выражения, не зависящие от типа (Type-generic expressions) с использованием ключевого слова _Generic Комплексные числа

Ответ 2



Язык C существенно отличается от языка C++ с самого начала его существования. (Понятно что новые свойства языка C99 позволяют нам запросто писать примеры C кода, который не будет компилироваться в C++, но на самом деле для этого совсем не обязательно обращаться к C99. Даже "классический" стандартный C - C89/90 - заметно отличается от C++.) Серьезных отличий много, но согласно постановке вопроса нас интересуют только отличия которые делают корректный C код некорректным в C++. Не претендуя на полноту, попробу перечислить эти отличия и привести примеры кода, опирающиеся на эти отличия. Ключевым моментом здесь является именно то, что использованные синтаксические конструкции присутствуют в обоих языках, т.е. с первого взгляда код выглядит достаточно невинно и с точки зрения языка C++. В языке C разрешается "терять" замыкающий '\0' при инициализации массива символов строковым литералом char s[4] = "1234"; Код некорректен с точки зрения C++. C поддерживает "предварительные" (tentative) определения. В одной единице трансляции можно сделать множественные внешние определения одного и того же объекта /* На уровне файла */ int a; int a; int a, a, a; Код некорректен с точки зрения C++. В языке С typedef-имена типов и тэги struct-типов располагаются в разных пространствах имен и не конфликтуют друг с другом. Например, такой набор объявлений допустим в С struct A { int i; }; typedef struct B { int i; } A; typedef struct C { int i; } C; В языке С++ не существует отдельного понятия "тэга" для класс-типов: имена классо разделяют одно пространство имен с typedef-именами и могут конфликтовать с ними. Дл частичной обратной совместимости с кросс-компилируемым идиоматическим С кодом язык С++ разрешает объявлять typedef-псевдонимы, совпадающие с именами существующих класс-типов, но только при условии, что псевдоним ссылается именно на класс-тип с таким же именем. В вышеприведенном примере первое typedef-объявление некорректно с точки зрения C++. В языке С "незнакомое" имя struct типа, упомянутое в списке параметров функции, являетс объявлением нового типа, локального для этой функции. При этом в списке параметров функции этот тип может быть объявлен как неполный, а "дообъявлен" до полного типа уже в теле функции /* Тип `struct S` в этой точке не известен */ void foo(struct S *p) { struct S { int a; } s; p = &s; p->a = 5; } В этом коде все корректно с точки зрения языка С: p имеет тот же тип, что и &s и т.д. С точки зрения языка C++ упоминание "незнакомого" имени struct типа в списке параметро функции тоже является объявлением нового типа. Однако этот новый тип не является локальным он считается принадлежащим охватывающему пространству имен. Поэтому с точки зрения языка C++ локальное определение типа S в вышеприведенном примере не имеет никакого отношения к типу S, упомянутому в списке параметров. Присваивание p = &s невозможно из-за несоответствия типов. Код некорректен с точки зрения C++. C разрешает делать определения новых типов внутри оператора приведения типа, внутри оператора sizeof, в объявлениях функций (типы возвращаемого значения и типы параметров) int a = sizeof(enum E { A, B, C }) + (enum X { D, E, F }) 0; enum E e = B; int b = e + F; Код некорректен с точки зрения C++. Язык С разрешает определять внешние объекты неполных типов при условии, что тип доопределяется и становится полным где-то дальше в этой же единице трансляции /* На уровне файла */ struct S s; struct S { int i; }; Вышеприведенный набор объявлений корректен с точки зрения С, но некорректен с точки зрения С++. Язык С++ безусловно запрещает определять объекты неполных типов. В языке C многие statements создают свою неявную охватывающую область видимости дополнение к уже существующей области видимости в "теле" этого statement, в то время как в C++ создается единая область видимости. Например for (int i = 0; i < 10; ++i) { int i = 42; } В языке C тело цикла является вложенной областью видимости по отношению к заголовк цикла, вследствие чего данный код является корректным. В C++ область видимости только одна, что исключает возможность "вложенного" объявления i. Язык C разрешает прыжки через объявления с инициализацией switch (1) { int a = 42; case 1:; } Код некорректен с точки зрения C++. В C вложенные объявления struct типов помещают имя внутреннего типа во внешнюю (охватывающую) область видимости struct A { struct B { int b; } a; }; struct B b; Код некорректен с точки зрения C++. C допускает неявное преобразование указателей из типа void * void *p = 0; int *pp = p; Код некорректен с точки зрения C++. C поддерживает объявления функций без прототипов /* На уровне файла */ void foo(); void bar() { foo(1, 2, 3); } Код некорректен с точки зрения C++. В C значения типа enum свободно неявно преобразуемы к типу int и обратно enum E { A, B, C } e = A; e = e + 1; Код некорректен с точки зрения C++. Неявно генерируемые компилятором C++ конструкторы копирования и операторы присваивания не умеют копировать volatile объекты. В C же копирование volatile объектов - не проблема struct S { int i; }; volatile struct S v = { 0 }; struct S s = v; Код некорректен с точки зрения C++. В C строковые литералы имеют тип char [N], а в C++ - const char [N]. Даже если считать что "классический" C++ в виде исключения поддерживает преобразование строкового литерала к типу char *, это исключение работает только тогда, когда оно применяется непосредственно к строковому литералу char *p = &"abcd"[0]; Код некорректен с точки зрения C++. C допускает использование "бессмысленных" спецификаторов класса хранения в объявлениях, которые не объявляют объектов static struct S { int i; }; Код некорректен с точки зрения C++. Дополнительно можно заметить, что в языке C typedef формально тоже является лиш одним из спецификаторов класса хранения, что позволяет создавать typedef-объявления, которые не объявляют псевдонимов typedef struct S { int i; }; C++ не допускает таких typedef-объявлений. С допускает явные повторения cv-квалификаторов в объявлениях const const int a = 42; Код некорректен с точки зрения C++. (С++ допускает аналогичную "избыточную" квалификацию, но только через посредство промежуточных имен типов: typedef-имена, параметры шаблонов). В языке C любое целочисленное константное выражение со значением 0 может использоваться в качестве null pointer constant void *p = 2 - 2; void *q = -0; Так же обстояли дела и в C++ до принятия стандарта C++11. Однако в современном C+ из целочисленных значений только буквальное значение 0 (целочисленный литерал) может выступать в роли null pointer constant, а вот более сложные выражения более не являются допустимыми. Вышеприведенные инициализации некорректны с точки зрения C++. В языке C вы можете сделать неопределяющее объявление объекта типа void extern void v; (Определение такого объекта сделать не получится, т.к. void - неполный тип). В C++ запрещается делать даже неопределяющее объявление. В языке С битовое поле, объявленное с типом int без явного указания signed или unsigne может быть как знаковым, там и беззнаковым (определяется реализацией). В языке С++ такое битовое поле всегда является знаковым. Препроцессор языка C не знаком с такими литералами как true и false. В языке C tru и false доступны лишь как макросы, определенные в заголовочном файле . Если эти макросы не определены, то в соответствии с правилами работы препроцессора, как #if true так и #if false должно вести себя как #if 0. В то же время препроцессор языка C++ обязан нативно распознавать литералы true false и его директива #if должна вести себя с этим литералами "ожидаемым" образом. Это может служить источником несовместимостей, когда в C-коде не произведено включение #if true int a[-1]; #endif Данный код является заведомо некорректным в C++, и в то же время может спокойно компилироваться в C. В языке С не поддерживается cv-квалификация для rvalues. В частности, cv-квалификаци возвращаемого значения функции игнорируется языком. Вкупе с автоматическим преобразованием массивов к указателям, это позволяет обходить некоторые правила константной корректности struct S { int a[10]; }; const struct S foo() { struct S s; return s; } int main() { int *p = foo().a; } С точки зрения языка C++ возвращаемое значение foo() и, следовательно, массив foo().a являются константными, и неявное преобразование foo().a к типу int * невозможно. В языке С неявный конфликт между внутренним и внешним связыванием при объявлени одной и той же сущности приводит к неопределенному поведению, а в С++ такой конфликт делает программу ill-formed (ошибочной). Чтобы устроить такой неявный конфликт, надо выстроить довольно хитрую конфигурацию static int a; /* Внутреннее связывание */ void foo(void) { int a; /* Скрывает внешнее static `a`, не имеет связывания */ { extern int a; /* Из-за того, что внешнее static `a` скрыто, объявляет `a` с внешним связыванием. Теперь `a` объявлено и с внешним, и с внутренним связыванием - конфликт */ } } В С++ такое extern-объявление является ошибочным. Рекурсивные вызовы функции main разрешены в C, но запрещены в C++. Препроцессор языка C++ больше не рассматривает (C++11) последовательность <строковы или символьный литерал><идентификатор> как независимые токены. С точки зрения языка C++ <идентификатор> в такой ситуации является суффиксом литерала. Чтобы избежать такой интерпретации, в языке C++ эти токены следует разделять пробелом uint32_t a = 42; printf("%"PRIu32, a); Этот код корректен c точки зрения C, но некорректен с точки зрения C++.

Ответ 3



А зачем вам это надо? Если у вас есть C-код, так компилируйте его как C. Если над использовать C++, то компилируйте разными компиляторами, а потом линкуйте отдельно. Не забудьте про extern "C" только, иначе линковка закончится ошибкой. UPD Если подумать чисто теоретически, то главной проблемой наверняка станет недостаточн строгая проверка типов в С, которую не потерпит С++. С++ в отношении типов более строгий. UPD2 И да, в С можно, например, вызывать необъявленные функции с неправильными аргументами. Ясно что С++ такое зарубит в тот же момент.

Ответ 4



Хороший вопрос. Всегда писал на с и избегал с++. Вот этот код (только не помню, зачем он, но компилируется gcc и работает) не компилируется g++. #include #include #include #include main () { int c, i = 0; char c4[5]; clock_t t = clock(); unsigned short *u = L"12345\0AРђР‘\n"; for (i = 0; u[i] != '\n'; i++) printf ("u[%d] = %d (%x)\n",i,u[i],u[i]); printf ("start %d %d\n",CLOCKS_PER_SEC,t); i = 0; while ((c = getch()) != 26 ) { // ^Z text stdin EOF printf ("%d %d\n",CLOCKS_PER_SEC,clock()); if (c >= '0' && c <= '9') { putchar(c); fflush(stdout); c4[i++] = c; } if (c == '\b') { // BS == 8 printf ("\b \b"); fflush(stdout); if (--i < 0) i = 0; continue; } if (c == '\r' || i == 4) { // ENTER or Your 4 digits c4[i] = 0; printf ("\nMy %d digits: %s\n",i,c4); i = 0; } } clock_t t1 = clock(); printf ("%d %d\n",CLOCKS_PER_SEC,t1); } MinGW g++ (GCC) 3.4.5 (mingw-vista special r3) в Windows XP g++ -c t1.c пишет: t1.c: In function `int main()': t1.c:11: error: invalid conversion from `const wchar_t*' to `short unsigned int*' t1.c:18: error: `getch' was not declared in this scope Так что, с точки зрения практики, @cy6erGn0m Вам все правильно сказал.

Ответ 5



Кроме всего вышеназванного, бывает, что попытка скомпилировать код C в C++ заканчиваетс ошибками из-за malloc, точнее из-за возможности соответствующей записи с void* в C и невозможности в C++: // C int* a = malloc(24 * sizeof(int)); // C++ (можно использовать и C-style каст) int* a = static_cast(malloc(24 * sizeof(int));) Но вообще, да - компилируйте отдельно соответствующим компилятором и линкуйтесь Тем более, что, например, C библиотеки, собранные с помощью gcc, icc и cl, в 99% случаев совместимы.

Ответ 6



Замечательный вопрос. Действительно, C не является подмножеством C++. Помимо хрестоматийног примера с приведением void* к типизированному указателю, есть ряд более редких, но иногда очень неприятных моментов. Вот код, который без ошибок обрабатывается компилятором gcc (в том числе с ключом --pedantic): #include int plus(); int main() { printf("%d\n", plus(5, 7)); return 0; } int plus(int a, int b) { return a + b; } Однако при компиляции g++ имеем: user@linux:~> g++ test.c test.c: In function ‘int main()’: test.c:7:27: error: too many arguments to function ‘int plus()’ test.c:3:5: note: declared here Компилятор C в соответствии со стандартом трактует пустой список параметров в прототип функции plus() как неопределенный, компилятор C++ - как пустой. Только при вызове компилятора C с ключом -Wall можно лицезреть предупреждение. Разумеется, любые соглашения о стиле кодирования должны исключать пустые прототипы поскольку компилятор лишается возможности проверки соответствия сигнатур типов, то есть в приведенном примере можно написать plus(5, 7, 9) вместо plus(5, 7) и компилятор прожует.

Ответ 7



gсс (на данный момент 7.3) в режиме с++ все еще не поддерживает некоторых способов инициализации структур/объединений/перечислений, которые поддерживаются в режиме с: struct array_s { union { // в объединении C++ может инициализироваться первый элемент struct { uint16_t af; uint16_t bf; }; uint32_t sign; }; uint16_t rows; uint16_t cols; void *data; }; #ifdef __cplusplus struct array_s a = {{{1, 2} }, 100, 200 }; // c++ // struct array_s a = {{{0x20001}}, 100, 200 }; #else struct array_s b = {{ .af = 1, .bf = 2 }, .cols = 200, .rows = 100}; // c #endif Поскольку, инициализация объединения в работает только для первого члена, в вышеприведенно случае это анонимная структура, инициализацию sign вместо структуры классическим способом выполнить невозможно ни в с, ни в с++. Однако это можно сделать в c11, как это показано выше. В c++ мы получаем ошибку: error: too many initializers for 'array_s' sorry, unimplemented: non-trivial designated initializers not supported

Комментариев нет:

Отправить комментарий