7

Может ли код, корректный как в C, так и в C++, вести себя по-разному при компиляции в каждом из языков?

1

Описание проблемы: Различия в поведении кода между C и C++

C и C++ имеют множество отличий, и не весь корректный код на C является корректным кодом на C++. Под "корректным" я имею в виду стандартный код с определенным поведением (т.е. не специфичный для реализации/неопределенный и т.д.).

Вопрос в том, существует ли сценарий, в котором фрагмент кода, корректный как в C, так и в C++, будет давать разное поведение при компиляции стандартным компилятором для каждого из языков?

Для того чтобы сравнение было уместным и полезным (я стремлюсь к практическому обучению, а не к поиску очевидных лазеек в вопросе), давайте предположим следующее:

  • Никакие препроцессорные директивы (это означает, что не будет хаков с использованием #ifdef __cplusplus, прагм и т.д.)
  • Все, что определяется реализацией, одинаково в обоих языках (например, численные пределы и т.д.)
  • Мы сравниваем разумно современные версии каждого языка (например, C++98 и C90 или более поздние версии). Если версии важны, просьба указать, какие версии приводят к различиям в поведении.

Надеюсь на ваши объяснения и примеры!

5 ответ(ов)

4

Вот пример, который иллюстрирует разницу между вызовами функций и объявлениями объектов в 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.

4

В 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. Конечно, я просто выбрал произвольные числа — вы можете использовать другие по своему усмотрению.

1

В 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++ основана на том, как компиляторы этих языков интерпретируют этот ключевое слово.

1

В другом примере, который я еще не видел упомянутым, показана разница в препроцессорах:

#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 считается истинным значением.

1

В соответствии со стандартом 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
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь