C и C ++ имеют много различий, и не весь действительный код C является допустимым кодом C ++.
(Под «допустимым» я подразумеваю стандартный код с определенным поведением, то есть не зависящим от реализации / неопределенным / и т. Д.)
Есть ли сценарий, в котором фрагмент кода, действительный как на C, так и на C ++, будет иметь разное поведение при компиляции со стандартным компилятором на каждом языке?
Чтобы сделать это разумное / полезное сравнение (я пытаюсь изучить что-то практически полезное, а не пытаться найти очевидные лазейки в вопросе), давайте предположим:
- Ничего не связанного с препроцессором (что означает отсутствие хаков
#ifdef __cplusplus
, прагм и т. Д.) - Все, что определяется реализацией, одинаково в обоих языках (например, числовые ограничения и т. Д.)
- Мы сравниваем разумно последние версии каждого стандарта (например, C ++ 98 и C90 или более поздние
версии ). Если версии имеют значение, то, пожалуйста, укажите, какие из версий имеют различное поведение.
Ответы:
Следующее, допустимое в C и C ++, (скорее всего) приведет к различным значениям
i
в C и C ++:См. Размер символа ('a') в C / C ++ для объяснения различия.
Еще один из этой статьи :
источник
struct
структурных имен.struct sz { int i[2];};
будет означать, что C и C ++ должны выдавать разные значения. (Принимая во внимание, что DSP с sizeof (int) == 1, мог бы произвести то же самое значение).Вот пример, который использует разницу между вызовами функций и объявлениями объектов в C и C ++, а также тот факт, что C90 позволяет вызывать необъявленные функции:
В C ++ это ничего не печатает, потому что временный объект
f
создается и уничтожается, но в C90 он печатает,hello
потому что функции могут быть вызваны без объявления.В случае, если вам интересно, что имя
f
используется дважды, стандарты C и C ++ явно разрешают это и создают объект, который вы должны сказать,struct f
чтобы устранить неоднозначность, если вы хотите структуру, или пропустить,struct
если вы хотите функцию.источник
Для C ++ и C90 есть как минимум один способ получить другое поведение, которое не определено реализацией. C90 не имеет однострочных комментариев. С небольшой осторожностью мы можем использовать это для создания выражения с совершенно разными результатами в C90 и C ++.
В C ++ все от
//
конца до конца строки является комментарием, так что это работает так:Поскольку C90 не имеет однострочных комментариев, это только
/* comment */
комментарий. Первый/
и2
оба являются частями инициализации, так что получается:Таким образом, правильный компилятор C ++ даст 13, но строго правильный компилятор C90 8. Конечно, я просто выбрал здесь произвольные числа - вы можете использовать другие числа по своему усмотрению.
источник
2
, он будет читаться как10 / + 3
действительный (унарный +).С90 против С ++ 11 (
int
противdouble
):В C
auto
означает локальную переменную. В C90 можно опустить переменную или тип функции. По умолчанию оноint
. В C ++ 11auto
означает нечто совершенно иное, он говорит компилятору выводить тип переменной из значения, используемого для ее инициализации.источник
auto
?int
по умолчанию. Это умно! +1int
.int
. Тем не менее, в реальном мире, где есть тонны унаследованного кода, а лидер рынка до сих пор не внедрил C99 и не собирается этого делать, говорить об «устаревшей версии C» абсурдно.Другой пример, о котором я еще не упоминал, этот, подчеркивающий разницу препроцессора:
Это печатает «false» в C и «true» в C ++ - в C любой неопределенный макрос оценивается в 0. В C ++ есть 1 исключение: «true» оценивается в 1.
источник
#define true false
ಠ_ಠСогласно стандарту C ++ 11:
а. Оператор запятой выполняет преобразование lvalue в rvalue в C, но не в C ++:
В C ++ значение этого выражения будет 100, а в C это будет
sizeof(char*)
.б. В C ++ тип перечислителя - это его перечисление. В С типом перечислителя является int.
Это означает, что
sizeof(int)
не может быть равенsizeof(E)
.с. В C ++ функция, объявленная с пустым списком параметров, не принимает аргументов. В C пустой список параметров означает, что количество и тип функциональных параметров неизвестен.
источник
sizeof(char*)
может быть 100, в этом случае первый пример будет производить то же наблюдаемое поведение в C и C ++ (то есть, хотя метод полученияs
будет другим,s
в конечном итоге будет 100). ОП упомянул, что этот тип поведения, определяемого реализацией, был хорош, поскольку он просто хотел избежать ответов юристов, поэтому первый вариант подходит для его исключения. Но второй хорош в любом случае.char arr[sizeof(char*)+1]; int s = sizeof(0, arr);
void *arr[100]
. В этом случае элемент имеет тот же размер, что и указатель на тот же элемент, поэтому, если имеется 2 или более элементов, массив должен быть больше адреса его первого элемента.Эта программа печатает
1
на C ++ и0
на C:Это происходит потому, что
double abs(double)
в C ++ есть перегрузка, поэтому онabs(0.6)
возвращается,0.6
тогда как в C он возвращается0
из-за неявного преобразования типа double в int перед вызовомint abs(int)
. В C вы должны использоватьfabs
для работыdouble
.источник
stdlib.h
только определяетabs(int)
иabs(long)
; версияabs(double)
объявленаmath.h
. Так что эта программа еще может называтьabs(int)
версию. Это деталь реализации, котораяstdlib.h
такжеmath.h
должна быть включена. (Я думаю, что это будет ошибка, если быabs(double)
были вызваны, но другие аспектыmath.h
не были включены).<math.h>
также включает в себя дополнительные перегрузки; на практике оказывается, что все основные компиляторы не включают эти перегрузки, если не используется форма<cmath>
.В C это печатает независимо от значения
sizeof(int)
текущей системы, которая обычно используется4
в большинстве современных систем.В C ++ это должно вывести 1.
источник
%d
неправильный спецификатор формата дляsize_t
.Еще одна
sizeof
ловушка: логические выражения.Это равно
sizeof(int)
C, потому что выражение имеет типint
, но обычно 1 в C ++ (хотя это не обязательно). На практике они почти всегда разные.источник
!
должен быть достаточно дляbool
.sizeof(0)
находится4
как в C, так и в C ++, потому что0
является целочисленным значением.sizeof(!0)
находится4
в C и1
в C ++. Логическое НЕ действует на операнды типа bool. Если значение типа int0
неявно преобразуется вfalse
(значение bool r), то оно переворачивается, что приводит кtrue
. Обаtrue
иfalse
bool rvalues в C ++ иsizeof(bool)
является1
. Однако в C!0
оценивается1
, что является значением типа int. Язык программирования C не имеет типа данных bool по умолчанию.Язык программирования C ++ (3-е издание) дает три примера:
sizeof ('a'), как упомянул @Adam Rosenfield;
//
комментарии, используемые для создания скрытого кода:Структуры и т. Д. Прячут вещи в области видимости, как в вашем примере.
источник
Старый каштан, который зависит от компилятора C, не распознает комментарии конца строки C ++ ...
источник
Еще один из перечисленных в стандарте C ++:
источник
x
наверху есть еще один . я думал, ты сказал "массивa
".Встроенные функции в C по умолчанию имеют внешнюю область, а не те, что в C ++.
Компилирование следующих двух файлов вместе напечатало бы «Я в строке» в случае GNU C, но ничего для C ++.
Файл 1
Файл 2
Кроме того, C ++ неявно обрабатывает любой
const
глобальныйstatic
объект, если он явно не объявленextern
, в отличие от C, которыйextern
используется по умолчанию.источник
extern
это демонстрируется здесь?struct fun
vsfn
) и не имеет никакого отношения к тому, является ли функция встроенной. Результат идентичен, если вы удалитеinline
классификатор.inline
не была добавлена до C99, но в C99fun()
не может быть вызвана без прототипа по объему. Поэтому я предполагаю, что этот ответ относится только к GNU C.Возвращает с кодом выхода 0 в C ++ или 3 в C.
Этот трюк, вероятно, можно было бы использовать для создания чего-то более интересного, но я не мог придумать хорошего способа создания конструктора, который был бы приемлем для C. Я попытался сделать такой же скучный пример с конструктором копирования, который позволил бы аргумент быть переданным, хотя довольно непереносимым способом:
VC ++ 2005 отказался компилировать это в режиме C ++, хотя и жаловался на то, как «код выхода» был переопределен. (Я думаю, что это ошибка компилятора, если я вдруг не забыл, как программировать.) Он завершился с кодом завершения процесса 1, хотя и скомпилирован как C.
источник
exit(code)
является допустимым объявлением переменнойcode
типаexit
, по-видимому. (См. «Самый неприятный анализ», это другая, но похожая проблема).Эта программа prints
128
(32 * sizeof(double)
) при компиляции с использованием компилятора C ++ и4
при компиляции с использованием компилятора C.Это потому, что у C нет понятия разрешения области. В Си структуры, содержащиеся в других структурах, попадают в область действия внешней структуры.
источник
32*sizeof(double)
а не 32, хотя :))size_t
с%d
Не забывайте о разнице между глобальными пространствами имен C и C ++. Предположим, у вас есть foo.cpp
и foo2.c
Теперь предположим, что у вас есть main.c и main.cpp, которые выглядят так:
При компиляции как C ++ он будет использовать символ в глобальном пространстве имен C ++; в C он будет использовать C one:
источник
foo
). Не существует отдельных «глобальных пространств имен».Это довольно своеобразно тем, что оно действительно в C ++ и в C99, C11 и C17 (хотя и необязательно в C11, C17); но не действует в C89.
В C99 + он создает массив переменной длины, который имеет свои особенности по сравнению с обычными массивами, поскольку имеет тип времени выполнения вместо типа времени компиляции и
sizeof array
не является целочисленным константным выражением в C. В C ++ тип является полностью статическим.Если вы попытаетесь добавить инициализатор здесь:
допустим C ++, но не C, потому что массивы переменной длины не могут иметь инициализатор.
источник
Это касается lvalues и rvalues в C и C ++.
В языке программирования C операторы предварительного увеличения и последующего увеличения возвращают значения, а не значения. Это означает, что они не могут быть слева от
=
оператора присваивания. Оба эти утверждения приведут к ошибке компилятора в C:Однако в C ++ оператор предварительного увеличения возвращает значение l , а оператор постинкремента возвращает значение r. Это означает, что выражение с оператором предварительного увеличения может быть размещено слева от
=
оператора присваивания!Теперь, почему это так? Постинкремент увеличивает переменную и возвращает переменную, которая была до того, как произошло увеличение. На самом деле это просто ценность. Прежнее значение переменной a копируется в регистр как временное, а затем увеличивается на a. Но прежнее значение a возвращается выражением, это rvalue. Он больше не представляет текущее содержимое переменной.
Предварительный инкремент сначала увеличивает переменную, а затем возвращает переменную такой, какой она стала после того, как произошло увеличение. В этом случае нам не нужно сохранять старое значение переменной во временном регистре. Мы просто получаем новое значение переменной после его увеличения. Таким образом, предварительное приращение возвращает lvalue, оно возвращает саму переменную a. Мы можем использовать присвоение этого lvalue чему-то другому, это похоже на следующее утверждение. Это неявное преобразование lvalue в rvalue.
Поскольку предварительное приращение возвращает lvalue, мы также можем присвоить ему что-то. Следующие два утверждения идентичны. Во втором присваивании сначала увеличивается значение a, затем его новое значение перезаписывается на 2.
источник
Пустые структуры имеют размер 0 в C и 1 в C ++:
источник