it-swarm.com.ru

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

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

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

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

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

Следующее, допустимое в 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;
}
378
Alexey Frunze

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

458
Seth Carnegie

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

417
Jerry Coffin

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

#include <stdio.h>

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

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

169
detunized

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

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

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

114
godlygeek

Согласно стандарту C++ 11:

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

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

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

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

Эта программа печатает 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;
}

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

51
Pavel Chikulaev

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

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

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

37
Alex B
#include <stdio.h>

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

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

В C++ это должно вывести 1.

36
Adam Rosenfield

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

  1. sizeof ('a'), как упомянул @Adam Rosenfield;

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

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

25
derobert

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

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

Еще один из перечисленных в стандарте 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)); 
}
20
Johannes Schaub - litb

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

20
fayyazkl
struct abort
{
    int x;
};

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

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

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

struct exit
{
    int x;
};

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

    exit(code);

    return 0;
}

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

16
please delete me
#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 нет понятия разрешения области. В Си структуры, содержащиеся в других структурах, попадают в область действия внешней структуры.

15
wefwefa3

Не забывайте различие между глобальными пространствами имен C и 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++; в C он будет использовать C one:

$ 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
7
user23614
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, потому что массивы переменной длины не может иметь инициализатор.

3
Antti Haapala

Это касается 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++ оператор предварительного увеличения возвращает lvalue, а оператор постинкрементного возвращает значение 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++.
1
Galaxy

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

#include <stdio.h>

typedef struct {} Foo;

int main()
{
    printf("%zd\n", sizeof(Foo));
    return 0;
}
0
Daniel Frużyński