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


этот вопрос уже есть ответ здесь:

  • Что делает оператор унарный плюс? 14 ответов

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

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

если вы не видите его: между d и e я писал * +, где +и было задумано.

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

8 74

8 ответов:

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

унарный + возвращает повышенное значение.
Унарный - возвращает отрицание:

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)

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

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

из проекта стандарта C++5.3.1[expr.унарный.op]:

операнд унарного оператора + должен иметь арифметическое, перечисление Без области видимости или тип указателя и результатом является значение аргумента. Целочисленное продвижение выполняется на целочисленных или перечислительных операндах. Тип результата - это тип повышенного операнда.

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

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

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

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

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

#include <iostream>

int main()
{
    unsigned  x1 = 1 ;

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

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

4294967295

посмотреть его в прямом эфире использование gcc на wandbox.

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

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

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

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

может быть достигнуто с помощью явного приведения:

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

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

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

унарный плюс-это историческая случайность и, как правило, бесполезно.

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

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

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

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

d * + e

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

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

а это:

d*++e;

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

  • d (операнд)
  • * (оператор умножения)
  • ++ (предварительно оператор инкремента )
    • e (операнд)

более того, этого:

d*+++e;

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

  • d (операнд)
  • * (оператор умножения)
  • ++ (предварительно оператор инкремента )
    • + (оператор "унарный плюс" )
      • e (операнд)

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

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

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

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

    movl    , -20(%ebp)
    movl    , -16(%ebp)
    movl    , -12(%ebp)
    movl    , -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) и т. д.

Это просто основы математики. Например:

5 * -4 = -20

5 * +4 = 5 * 4 = 20 

-5 * -4 = 20

Минус * Минус = Плюс

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

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

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

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

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

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