Что здесь делает компилятор: int a = b * (c * d * + e)?

У меня была странная ошибка в моей программе, и после нескольких часов отладки я нашел следующую очень тупую строку:

int a = b * (c * d *  + e)

Если вы этого не видите: между d и e я написал * +, где был предназначен только a +.

Почему эта компиляция и что она на самом деле означает?

Ответ 1

+ интерпретируется как унарный плюс. Он просто возвращает значение продвинутого своего операнда.

Ответ 2

Как объяснил Брайан, он возвращает продвинутое значение.
Если это было -, оно вернет отрицание:

int a = 5;
int b = 6;
unsigned int c = 3;

std::cout << (a * +b); // = 30
std::cout << (a * -b); // = -30
std::cout << (1 * -c); // = 4294967293 (2^32 - 3)

Ответ 3

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

Предполагая, что e является интегральным или неперечисленным типом перечисления, в конечном итоге будет иметь интегральные поощрения, поскольку * применяет обычные арифметические преобразования к его операндам, которые заканчиваются интегральными акциями для интегральных типов.

Из проекта стандарта С++ 5.3.1 [expr.unary.op]:

Операнд унарного + оператора должен иметь арифметическое, неперечисленное перечисление или тип указателя, а результатом является значение аргумента. Интегральное продвижение выполняется на интегральных или перечисляющих операндах. Тип результата - тип продвинутого операнда.

Интегральные рекламные акции рассматриваются в разделе 4.5 [conv.prom], и если переменные e являются типом, отличным от bool, char16_t, char32_t, or wchar_t и имеют ранг преобразования меньше, чем int, то он будет охватываться абзацем 1

Значение целочисленного типа, отличного от bool, char16_t, char32_t или wchar_t, целочисленное преобразование rank (4.13) меньше ранга int может быть преобразован в prvalue типа int, если int может представлять все значения типа источника; в противном случае исходное значение prvalue может быть преобразовано в prvalue типа unsigned внутр.

Для полного набора случаев мы можем посмотреть cppreference.

Унарный плюс также может быть полезен в некоторых случаях для устранения двусмысленности, интересным случаем будет Разрешение двусмысленной перегрузки на указатель на функцию и std:: function для лямбды с использованием +.

Обратите внимание, что для этих ответов, относящихся к унарным - и отрицательным значениям, это вводит в заблуждение, как показывает этот пример:

#include <iostream>

int main()
{
    unsigned  x1 = 1 ;

    std::cout <<  -x1 << std::endl ;
}

что приводит к:

4294967295

Смотрите live используя gcc на wandbox.

Интересно отметить, что унарный плюс был добавлен в C99 для симметрии с унарным минусом, из Обоснование для языков международного стандартного программирования-C:

Унарный плюс был принят Комитетом C89 из нескольких реализаций, для симметрии с унарным минусом.

и я не могу придумать хороший случай, когда кастинг будет недостаточным для достижения того же желаемого продвижения/конверсии. Пример lambda, который я цитирую выше, используя унарный плюс, чтобы заставить лямбда-выражение быть преобразованным в указатель функции:

foo( +[](){} ); // not ambiguous (calls the function pointer overload)

может быть выполнено с использованием явного приведения:

foo( static_cast<void (*)()>( [](){} ) );

и можно утверждать, что этот код лучше, поскольку намерение явно.

Стоит отметить, что Аннотированное справочное руководство по С++ (ARM) содержит следующий комментарий:

Унарный плюс - историческая авария и вообще бесполезен.

Ответ 4

Как они объяснили, (+) и (-) были просто использованы как унарный оператор:

Унарные операторы действуют только на одном операнде в выражении

int value = 6;
int negativeInt = -5;
int positiveInt = +5;

cout << (value * negativeInt); // 6 * -5 = -30
cout << (value * positiveInt); // 6 * +5 = 30

cout << (value * - negativeInt); // 6 * -(-5) = 30
cout << (value * + negativeInt); // 6 * +(-5) = -30

cout << (value * - positiveInt); // 6 * -(+5) = -30
cout << (value * + positiveInt); // 6 * +(+5) = 30

поэтому из вашего кода:

int b = 2;
int c = 3;
int d = 4;
int e = 5;

int a = b * (c * d *  + e)

//result: 2 * (3 * 4 * (+5) ) = 120

Ответ 5

Почему он компилируется? Он компилируется, потому что + анализируется как оператор унарного плюс, а не оператор сложения. Компилятор пытается анализировать как можно больше, не генерируя синтаксических ошибок. Итак:

d * + e

Анализируется как:

  • d (операнд)
  • * (оператор умножения)
  • + (оператор унарного плюса)
    • e (операнд)

В то время как это:

d*++e;

Анализируется как:

  • d (операнд)
  • * (оператор умножения)
  • ++ (оператор pre increment)
    • e (операнд)

Кроме того, это:

d*+++e;

Анализируется как:

  • d (операнд)
  • * (оператор умножения)
  • ++ (оператор pre increment)
    • + (оператор унарного плюса)
      • e (операнд)

Обратите внимание, что он не создает синтаксическую ошибку, кроме ошибки компилятора "LValue requrired".

Ответ 6

Чтобы еще раз подправить правильные ответы, уже приведенные здесь, если вы скомпилируете флаг -s, компилятор C выведет файл сборки, в котором могут быть исследованы фактические генерируемые команды. Со следующим кодом C:

int b=1, c=2, d=3, e=4;
int a = b * (c * d *  + e);

Сгенерированная сборка (с использованием gcc, компиляция для amd64) начинается с:

    movl    $1, -20(%ebp)
    movl    $2, -16(%ebp)
    movl    $3, -12(%ebp)
    movl    $4, -8(%ebp)

чтобы мы могли идентифицировать отдельные позиции памяти -20 (% ebp) как переменные b, вплоть до -8 (% ebp) как переменные e. -4 (% epp) является переменной a. Теперь вычисление выполняется как:

    movl    -16(%ebp), %eax
    imull   -12(%ebp), %eax
    imull   -8(%ebp), %eax
    imull   -20(%ebp), %eax
    movl    %eax, -4(%ebp)

Итак, как было прокомментировано другими людьми, отвечающими, компилятор просто рассматривает "+ e" как унарную положительную операцию. Первая команда movl помещает содержимое переменной e в регистр аккумуляторов EAX, который затем быстро умножается на содержимое переменной d или -12 (% ebp) и т.д.

Ответ 7

Это просто базовая математика. Например:

5 * -4 = -20

5 * +4 = 5 * 4 = 20 

-5 * -4 = 20

Отрицательный * Отрицательный = Положительный

Положительный * Отрицательный = Отрицательный

Позитивный * Положительный = Положительный

Это самое простое объяснение.

Минус (-) и плюс (+) просто указывают, положительно или отрицательно.

Ответ 8

Оператор + между d и e будет рассматриваться как унарный + оператор, который будет определять только знак e. Поэтому компилятор увидит это утверждение следующим образом:

int a = b*(c*d*e) ;