Может ли код, корректный как в C, так и в C++, вести себя по-разному при компиляции в каждом из языков?
Описание проблемы: Различия в поведении кода между C и C++
C и C++ имеют множество отличий, и не весь корректный код на C является корректным кодом на C++. Под "корректным" я имею в виду стандартный код с определенным поведением (т.е. не специфичный для реализации/неопределенный и т.д.).
Вопрос в том, существует ли сценарий, в котором фрагмент кода, корректный как в C, так и в C++, будет давать разное поведение при компиляции стандартным компилятором для каждого из языков?
Для того чтобы сравнение было уместным и полезным (я стремлюсь к практическому обучению, а не к поиску очевидных лазеек в вопросе), давайте предположим следующее:
- Никакие препроцессорные директивы (это означает, что не будет хаков с использованием
#ifdef __cplusplus
, прагм и т.д.) - Все, что определяется реализацией, одинаково в обоих языках (например, численные пределы и т.д.)
- Мы сравниваем разумно современные версии каждого языка (например, C++98 и C90 или более поздние версии). Если версии важны, просьба указать, какие версии приводят к различиям в поведении.
Надеюсь на ваши объяснения и примеры!
5 ответ(ов)
Вот пример, который иллюстрирует разницу между вызовами функций и объявлениями объектов в C и C++, а также тот факт, что стандарт C90 позволяет вызывать неявно объявленные функции:
#include <stdio.h>
struct f { int x; };
int main() {
f();
}
int f() {
return printf("hello");
}
В C++ этот код не выведет ничего на экран, потому что будет создана и уничтожена временная переменная f
. Однако в C90 он выведет hello
, поскольку в этом стандарте разрешено вызывать функции, не объявленные заранее.
Что касается одновременного использования имени f
, то стандарты C и C++ это явно разрешают. Для создания объекта необходимо использовать struct f
для однозначности, если вы хотите, чтобы это был объект структуры. Если же вам нужна функция, просто опустите struct
.
В C++ и C90 действительно есть способ получить различное поведение, которое не является зависимым от реализации. В C90 отсутствуют однострочные комментарии. При некотором внимании мы можем создать выражение, которое будет давать совершенно разные результаты в C90 и C++.
Рассмотрим следующий код:
int a = 10 //* comment */ 2
+ 3;
В C++ всё, что находится после //
до конца строки, считается комментарием, поэтому этот код будет интерпретироваться как:
int a = 10 + 3;
В то время как в C90 однострочных комментариев нет, и только /* comment */
считается комментарием. Первый /
и 2
остаются частью инициализации, и в итоге мы получаем:
int a = 10 / 2 + 3;
Таким образом, корректный компилятор C++ возвратит значение 13, в то время как строгий компилятор C90 возвратит 8. Конечно, я просто выбрал произвольные числа — вы можете использовать другие по своему усмотрению.
В C auto
обозначает локальную переменную, но не имеет никакого специального значения, как в C++. В стандарте C90 не допускается опускать тип переменной или функции, и по умолчанию тип переменной будет int
.
Ваш код:
#include <stdio.h>
int main()
{
auto j = 1.5;
printf("%d", (int)sizeof(j));
return 0;
}
в C90 будет скомпилирован с ошибкой, так как j
не имеет определённого типа, и компилятор по умолчанию считает её int
, что неправильно. Если вы хотите определить j
как double
, вам нужно явно указать тип, например, double j = 1.5;
.
С другой стороны, в C11 auto
имеет совершенно другое значение. Он позволяет компилятору вывести тип переменной на основе значения, которым она инициализируется. В вашем примере компилятор C11 выведет j
как double
, потому что 1.5
является литералом типа double
.
В C++11 ваш код будет выглядеть так:
#include <iostream>
int main()
{
auto j = 1.5; // j будет иметь тип double
std::cout << sizeof(j) << std::endl; // выведет 8 (обычно для double)
return 0;
}
Таким образом, разница в использовании auto
между C и C++ основана на том, как компиляторы этих языков интерпретируют этот ключевое слово.
В другом примере, который я еще не видел упомянутым, показана разница в препроцессорах:
#include <stdio.h>
int main()
{
#if true
printf("true!\n");
#else
printf("false!\n");
#endif
return 0;
}
Этот код выводит "false" в C и "true" в C++. В стандарте C любое не определенное макрос выражается как 0. Но в C++ есть одно исключение: "true" оценивается как 1.
Таким образом, если вы используете условную компиляцию с #if true
, то результат будет зависеть от языка, в котором вы пишете код. В C это будет "false", так как true
не определен, а в C++ — "true", так как true
считается истинным значением.
В соответствии со стандартом C++11:
a. Оператор запятой выполняет преобразование lvalue в rvalue в C, но не в C++:
char arr[100];
int s = sizeof(0, arr); // Используется оператор запятой.
В C++ значение этого выражения будет 100, а в C — sizeof(char*)
.
b. В C++ тип перечисляемого значения соответствует его перечислению, тогда как в C тип перечисляемого значения — это int.
enum E { a, b, c };
sizeof(a) == sizeof(int); // В C
sizeof(a) == sizeof(E); // В C++
Это означает, что sizeof(int)
может не равняться sizeof(E)
.
c. В C++ функция, объявленная с пустым списком параметров, не принимает аргументов. В C пустой список параметров означает, что количество и тип параметров функции неизвестны.
int f(); // int f(void) в C++
// int f(*неизвестно*) в C
Как изменить цвет вывода echo в Linux
Разница между const int*, const int * const и int * const?
Почему переменные нельзя объявлять в операторе switch?
Что такое ошибка сегментации?
Разница между статическими и динамическими (разделяемыми) библиотеками?