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

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

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

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

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

Ответ 1

Следующее, действительное в 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;
}

Ответ 2

Вот пример, который использует разницу между вызовами функций и объявлениями объектов в C и С++, а также тот факт, что C90 позволяет вызывать необъявленные функции:

#include <stdio.h>

struct f { int x; };

int main() {
    f();
}

int f() {
    return printf("hello");
}

В С++ это ничего не будет печатать, потому что временный f создается и уничтожается, но на C90 он печатает hello, потому что функции могут быть вызваны без объявления.

В случае, если вам было интересно, что имя f используется дважды, стандарты C и С++ явно разрешают это, и чтобы сделать объект, который вы должны сказать struct f, чтобы устранить неоднозначность, если вы хотите создать структуру, или оставьте struct, если вы хотите эту функцию.

Ответ 3

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

Ответ 4

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

#include <stdio.h>

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

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

Ответ 5

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

#include <stdio.h>
int main()
{
#if true
    printf("true!\n");
#else
    printf("false!\n");
#endif
    return 0;
}

Это печатает "false" в C и "true" в С++. В C любой макрос undefined оценивается равным 0. В С++ существует 1 исключение: "true" оценивается в 1.

Ответ 6

В стандарте С++ 11:

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

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

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

b. В С++ тип перечислителя - это его перечисление. В 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

Ответ 7

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

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

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

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

Ответ 8

Другая ловушка sizeof: логические выражения.

#include <stdio.h>
int main() {
    printf("%d\n", (int)sizeof !0);
}

Он равен sizeof(int) в C, потому что выражение имеет тип int, но обычно 1 в С++ (хотя это и не требуется). На практике они почти всегда разные.

Ответ 9

#include <stdio.h>

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

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

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

Ответ 10

Язык программирования С++ (3-е издание) дает три примера:

  • sizeof ('a'), как упоминал @Adam Rosenfield,

  • // комментарии, используемые для создания скрытого кода:

    int f(int a, int b)
    {
        return a //* blah */ b
            ;
    }
    
  • Структуры и т.д. скрывают вещи вне областей, как в вашем примере.

Ответ 11

Старый каштан, который зависит от компилятора C, не распознавая комментарии конца строки С++...

...
int a = 4 //* */ 2
        +2;
printf("%i\n",a);
...

Ответ 12

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

#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)); 
}

Ответ 13

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

Компиляция следующих двух файлов вместе приведет к печати "Я встроен" в случае GNU 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");
} 

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

Ответ 14

struct abort
{
    int x;
};

int main()
{
    abort();
    return 0;
}

Возвращает с кодом выхода 0 в С++ или 3 в C.

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

struct exit
{
    int x;
};

int main()
{
    struct exit code;
    code.x=1;

    exit(code);

    return 0;
}

VС++ 2005 отказался компилировать, что в режиме С++, однако, жалуется на то, как переопределился код выхода. (Я думаю, что это ошибка компилятора, если только я не забыл, как программировать.) Он вышел с кодом завершения процесса 1, когда скомпилирован как C, хотя.

Ответ 15

#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)) при компиляции с использованием компилятора С++ и 4 при компиляции с использованием компилятора C.

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

Ответ 16

Не забывайте о различии между глобальными пространствами имен C и С++. Предположим, что у вас есть foo.cpp

#include <cstdio>

void foo(int r)
{
  printf("I am C++\n");
}

и foo2.c

#include <stdio.h>

void foo(int r)
{
  printf("I am C\n");
}

Теперь предположим, что у вас есть main.c и main.cpp, которые выглядят следующим образом:

extern void foo(int);

int main(void)
{
  foo(1);
  return 0;
}

При компиляции как С++ он будет использовать символ в глобальном пространстве имен С++; в C он будет использовать C один:

$ diff main.cpp main.c
$ gcc -o test main.cpp foo.cpp foo2.c
$ ./test 
I am C++
$ gcc -o test main.c foo.cpp foo2.c
$ ./test 
I am C

Ответ 17

int main(void) {
    const int dim = 5; 
    int array[dim];
}

Это довольно необычно тем, что оно действительно в C++ и в C99, C11 и C17 (хотя и необязательно в C11, C17); но не действует в C89.

В C99+ он создает массив переменной длины, который имеет свои особенности по сравнению с обычными массивами, поскольку он имеет тип времени выполнения вместо типа времени компиляции, а sizeof array не является целочисленным константным выражением в C. В C++ тип полностью статичен.


Если вы попытаетесь добавить инициализатор здесь:

int main(void) {
    const int dim = 5; 
    int array[dim] = {0};
}

допустимо C++, но не C, потому что массивы переменной длины не могут иметь инициализатор.

Ответ 18

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

В языке программирования C операторы предварительного увеличения и последующего увеличения возвращают значения, а не значения. Это означает, что они не могут быть слева от оператора присваивания =. Оба эти утверждения приведут к ошибке компилятора в 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++ оператор предварительного увеличения возвращает значение l, в то время как оператор последующего увеличения возвращает значение r. Это означает, что выражение с оператором предварительного приращения может быть размещено слева от оператора = присваивания!

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

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

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

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

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

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

Ответ 19

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

#include <stdio.h>

typedef struct {} Foo;

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