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




программирование на си (12)

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

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

Чтобы сделать это разумным / полезным сравнением (я пытаюсь узнать что-то практически полезное, а не пытаться найти очевидные лазейки в вопросе), давайте предположим:

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

C90 против C ++ 11 ( int vs. double ):

#include <stdio.h>

int main()
{
  auto j = 1.5;
  printf("%d", (int)sizeof(j));
  return 0;
}

В C auto локальная переменная. В C90 можно опустить переменную или тип функции. По умолчанию используется значение int . В C ++ 11 auto означает нечто совершенно другое, он сообщает компилятору вывести тип переменной из значения, используемого для ее инициализации.


Per C ++ 11:

а. Оператор запятой выполняет преобразование lvalue-rvalue в C, но не C ++:

   char arr[100];
   int s = sizeof(0, arr);       // The comma operator is used.

В C ++ значение этого выражения будет равно 100, а в C это будет sizeof(char*) .

б. В C ++ тип перечислителя - это его перечисление. В C тип перечислителя - int.

   enum E { a, b, c };
   sizeof(a) == sizeof(int);     // In C
   sizeof(a) == sizeof(E);       // In C++

Это означает, что sizeof(int) может быть не равно sizeof(E) .

с. В C ++ функция, объявленная с пустым списком параметров, не принимает аргументов. В C пустых параметрах список означает, что число и тип параметров функции неизвестны.

   int f();           // int f(void) in C++
                      // int f(*unknown*) in C

Встроенные функции в C по умолчанию для внешней области, где, как и в C ++, нет.

Компиляция следующих двух файлов вместе приведет к печати «Я встроен» в случае GNU C, но ничего для C ++.

Файл 1

#include <stdio.h>

struct fun{};

int main()
{
    fun();  // In C, this calls the inline function from file 2 where as in C++
            // this would create a variable of struct fun
    return 0;
}

Файл 2

#include <stdio.h>
inline void fun(void)
{
    printf("I am inline\n");
} 

Кроме того, C ++ неявно рассматривает любой const глобальный как static если он явно не объявлен extern , в отличие от C, в котором extern является значением по умолчанию.


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


Другой, указанный в стандарте C ++:

#include <stdio.h>

int x[1];
int main(void) {
    struct x { int a[2]; };
    /* size of the array in C */
    /* size of the struct in C++ */
    printf("%d\n", (int)sizeof(x)); 
}

Еще один пример, о котором я еще не упоминал, подчеркивает препроцессорную разницу:

#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.


Пустые структуры имеют размер 0 в C и 1 в C ++:

#include <stdio.h>

typedef struct {} Foo;

int main()
{
    printf("%zd\n", sizeof(Foo));
    return 0;
}

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

int i = sizeof('a');

См. « Размер символа» («a») в C / C ++ для объяснения разницы.

Еще один из этой статьи :

#include <stdio.h>

int  sz = 80;

int main(void)
{
    struct sz { char c; };

    int val = sizeof(sz);      // sizeof(int) in C,
                               // sizeof(struct sz) in C++
    printf("%d\n", val);
    return 0;
}

Эта программа печатает 1 в C ++ и 0 в C:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int d = (int)(abs(0.6) + 0.5);
    printf("%d", d);
    return 0;
}

Это происходит потому, что в C ++ имеется double abs(double) перегрузка, поэтому abs(0.6) возвращает 0.6 а в C возвращает 0 из-за неявного преобразования double-to-int перед вызовом int abs(int) . В C вы должны использовать fabs для работы с double .


Это касается lvalues ​​и rvalues ​​в C и C ++.

На языке программирования C оба параметра pre-increment и post-increment возвращают значения r, а не lvalues. Это означает, что они не могут находиться в левой части оператора = присваивания. Оба этих утверждения дадут ошибку компилятора в C:

int a = 5;
a++ = 2;  /* error: lvalue required as left operand of assignment */
++a = 2;  /* error: lvalue required as left operand of assignment */

Однако на C ++ оператор pre-increment возвращает lvalue , а оператор post-increment возвращает rvalue. Это означает, что выражение с оператором pre-increment можно поместить в левой части оператора = присваивания!

int a = 5;
a++ = 2;  // error: lvalue required as left operand of assignment
++a = 2;  // No error: a gets assigned to 2!

Теперь почему это так? Пост-инкремент увеличивает эту переменную и возвращает переменную, как это было до того, как произошло приращение. Это на самом деле просто rvalue. Первое значение переменной a копируется в регистр как временное, а затем увеличивается. Но прежнее значение a возвращается выражением, это rvalue. Он больше не представляет текущее содержимое переменной.

Предварительное приращение сначала увеличивает эту переменную, а затем возвращает переменную, как только после того, как произошло приращение. В этом случае нам не нужно хранить старое значение переменной во временном регистре. Мы просто извлекаем новое значение переменной после того, как оно было увеличено. Таким образом, pre-increment возвращает lvalue, он возвращает переменную a. Мы можем использовать присваивание этому lvalue чему-то другому, это похоже на следующий оператор. Это неявное преобразование lvalue в rvalue.

int x = a;
int x = ++a;

Поскольку pre-increment возвращает lvalue, мы можем также присвоить ему что-то. Следующие два утверждения идентичны. Во втором присваивании сначала выполняется инкремент a, а его новое значение заменяется на 2.

int a;
a = 2;
++a = 2;  // Valid in C++.

#include <stdio.h>

int main(void)
{
    printf("%d\n", (int)sizeof('a'));
    return 0;
}

В C это печатает независимо от значения sizeof(int) в текущей системе, которое обычно 4 в большинстве систем, которые обычно используются сегодня.

В C ++ это должно печатать 1.


#include <stdio.h>

struct A {
    double a[32];
};

int main() {
    struct B {
        struct A {
            short a, b;
        } a;
    };
    printf("%d\n", sizeof(struct A));
    return 0;
}

Эта программа печатает 128 ( 32 * sizeof(double) ) при компиляции с использованием компилятора C ++ и 4 при компиляции с использованием компилятора C.

Это связано с тем, что C не имеет понятия разрешения области. В C-структурах, содержащихся в других структурах, попадают в сферу внешней структуры.







c