Какие элементы языка С являются неподдерживаемыми в С++? Какой код на С не буде
принят компилятором С++? Особенно интересует поведение 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
Комментариев нет:
Отправить комментарий