Компилятор C ++ удаляет / оптимизирует бесполезные скобки?

19

Будет ли код

int a = ((1 + 2) + 3); // Easy to read

бежать медленнее, чем

int a = 1 + 2 + 3; // (Barely) Not quite so easy to read

или современные компиляторы достаточно умны, чтобы удалить / оптимизировать «бесполезные» скобки.

Это может показаться крошечной проблемой оптимизации, но выбор C ++ вместо C # / Java / ... полностью связан с оптимизацией (IMHO).

саржа
источник
9
Я думаю, что C # и Java тоже это оптимизируют. Я верю, что когда они анализируют и создают AST, они просто удаляют очевидные бесполезные вещи, подобные этому.
Фарид Нури Нешат
5
Все, что я прочитал, указывает на компиляцию JIT, которая легко опережает своевременную компиляцию, так что сама по себе это не очень убедительный аргумент. Вы начинаете программировать игры - настоящая причина, по которой вы предпочитаете преждевременную компиляцию, заключается в том, что она предсказуема - с JIT-компиляцией вы никогда не узнаете, когда компилятор начнет работать и попытается начать компиляцию кода. Но я хотел бы отметить, что преждевременная компиляция в нативный код не является взаимоисключающей с сборкой мусора, см., Например, Стандартные ML и D. И я видел убедительные аргументы, что сборка мусора более эффективна ...
Doval,
7
... чем RAII и умные указатели, так что это больше вопрос следования хорошо проработанному пути (C ++) по сравнению с относительно непрочным путем создания игрового программирования на этих языках. Я также хотел бы отметить, что беспокойство по поводу скобок безумно - я вижу, откуда вы, но это нелепая микрооптимизация. Выбор структур данных и алгоритмов в вашей программе определенно будет влиять на производительность, а не на такие мелочи.
Довал
6
Хм ... Какую оптимизацию вы ожидаете? Если вы говорите о статическом анализе, то в большинстве известных мне языков его заменит статически известный результат (реализации на основе LLVM даже принудительно применяют это, AFAIK). Если вы говорите о порядке выполнения, это не имеет значения, поскольку это та же самая операция и без побочных эффектов. В любом случае для сложения нужны два операнда. И если вы используете это для сравнения C ++, Java и C # с точки зрения производительности, похоже, у вас нет четкого представления о том, что такое оптимизации и как они работают, поэтому вам следует сосредоточиться на изучении этого.
Теодорос Чатзигианнакис,
5
Интересно, почему а) вы считаете заключенное в скобки выражение более читабельным (для меня это просто выглядит некрасиво, вводит в заблуждение (почему они подчеркивают этот конкретный порядок? Разве это не должно быть вспомогательным здесь?) И неуклюжим) б) почему вы думаете без в скобках он может работать лучше (ясно, что разбирать парены проще для машины, чем размышлять о фиксированности оператора. Однако, как говорит Марк ван Левен, это совершенно не влияет на время выполнения).
оставил около

Ответы:

87

Компилятор фактически никогда не вставляет и не удаляет скобки; он просто создает дерево разбора (в котором нет круглых скобок), соответствующее вашему выражению, и при этом он должен учитывать написанные вами скобки. Если вы полностью заключите в скобки свое выражение, то читателю также сразу станет понятно, что это за дерево разбора; если вы пойдете до крайности, введя явно избыточные круглые скобки, как в случае, int a = (((0)));то вы будете создавать некоторые бесполезные нагрузки на нейроны считывателя, в то же время тратя впустую некоторые циклы в анализаторе, не изменяя при этом результирующее дерево анализа (и, следовательно, сгенерированный код). ) ни малейшего

Если вы не пишете никаких скобок, то парсер все равно должен выполнять свою работу по созданию дерева разбора, а правила приоритета операторов и ассоциативности точно указывают ему, какое дерево разбора он должен построить. Вы могли бы рассмотреть эти правила как указание компилятору, какие (неявные) круглые скобки он должен вставить в ваш код, хотя в этом случае синтаксический анализатор фактически никогда не имеет дело с круглыми скобками: он просто создан для создания того же дерева разбора, как если бы круглые скобки присутствовали в определенных местах. Если вы поместите круглые скобки именно в те места, как в int a = (1+2)+3;(ассоциативность +is слева), то синтаксический анализатор получит тот же результат по несколько другому маршруту. Если вы ставите в разные скобки, как вint a = 1+(2+3);тогда вы форсируете другое дерево разбора, что, возможно, приведет к генерации другого кода (хотя, возможно, нет, поскольку компилятор может применять преобразования после построения дерева разбора, если эффект от выполнения результирующего кода никогда не будет другим для Это). Предположим, что есть разница в исходном коде, и вообще ничего нельзя сказать о том, что является более эффективным; Самым важным моментом, конечно, является то, что в большинстве случаев деревья разбора не дают математически эквивалентных выражений, поэтому сравнение скорости их выполнения не имеет смысла: нужно просто написать выражение, которое дает правильный результат.

Таким образом, результат таков: используйте круглые скобки по мере необходимости для корректности и по желанию для удобства чтения; если они избыточны, они никак не влияют на скорость выполнения (и незначительно влияют на время компиляции).

И ничего из этого не имеет ничего общего с оптимизацией , которая происходит после того, как дерево разбора было построено, поэтому оно не может знать, как было построено дерево разбора. Это относится без изменений от самых старых и глупых компиляторов к самым умным и самым современным. Только в интерпретируемом языке (где «время компиляции» и «время выполнения» совпадают) может быть наказание за избыточные скобки, но даже тогда я думаю, что большинство таких языков организованы так, что по крайней мере фаза синтаксического анализа выполняется только один раз для каждого оператора (сохраняя предварительно проанализированную форму для выполнения).

Марк ван Леувен
источник
с / oldes / старейшей /. Хороший ответ, +1.
Дэвид Конрад,
23
Полное предупреждение: «Вопрос не очень хорошо поставлен». Я не согласен, принимая «хорошо поставленный», чтобы означать «ясно указывающий, какой пробел в знаниях хочет заполнить этот кверент». По сути, вопрос заключается в том, чтобы «оптимизировать для X, выбрать A или B и почему? Что происходит под ним?», Что, по крайней мере для меня, очень ясно показывает, что такое пробел в знаниях. Недостаток в вопросе, на который вы правильно указали и который очень хорошо решен, заключается в том, что он построен на неверной ментальной модели.
Йонас Кёлкер,
Для a = b + c * d;, a = b + (c * d);было бы [безвредно] избыточные скобки. Если они помогут вам сделать код более читабельным, хорошо. a = (b + c) * d;были бы лишними круглыми скобками - они фактически меняют результирующее дерево разбора и дают другой результат. Совершенно законно делать (на самом деле, необходимо), но они не совпадают с подразумеваемой группировкой по умолчанию.
Фил Перри
1
@OrangeDog правда, это жаль, что комментарий Бена выявил несколько людей, которые любят утверждать, что виртуальные машины работают быстрее, чем нативные.
gbjbaanb
1
@ JonasKölker: Мое вступительное предложение на самом деле относится к вопросу, сформулированному в заголовке: на самом деле нельзя ответить на вопрос о том, вставляет или удаляет компилятор скобки, поскольку это основано на неправильном представлении о том, как работают компиляторы. Но я согласен, что совершенно ясно, какой пробел в знаниях необходимо устранить.
Марк ван Леувен
46

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

К вашему сведению, компилятор достаточно умен, чтобы полностью оптимизировать его, если может. В ваших примерах это может быть использовано во int a = 6;время компиляции.

gbjbaanb
источник
9
Абсолютно - вставьте столько скобок, сколько захотите, и позвольте компилятору выполнить тяжелую работу по выяснению того, что вы хотите :)
gbjbaanb
23
@ Истинное программирование @Serge больше относится к удобочитаемости вашего кода, чем к производительности. Вы будете ненавидеть себя в следующем году, когда вам нужно будет отладить сбой, и у вас есть только «оптимизированный» код.
фрик с трещоткой
1
@ratchetfreak, ты прав, но я также знаю, как комментировать мой код. int a = 6; // = (1 + 2) + 3
Серж
20
@ Серж, я знаю, что это не выдержит после года твиков, со временем комментарии и код выйдут из синхронизации, и тогда у вас получитсяint a = 8;// = 2*3 + 5
ratchet freak
21
или с сайта www.thedailywtf.com:int five = 7; //HR made us change this to six...
мычанка
23

Ответ на фактически заданный вами вопрос - нет, но ответ на вопрос, который вы хотели задать, - да. Добавление скобок не замедляет код.

Вы задали вопрос об оптимизации, но скобки не имеют ничего общего с оптимизацией. Компилятор применяет различные методы оптимизации с целью улучшить размер или скорость сгенерированного кода (иногда оба). Например, он может взять выражение A ^ 2 (квадрат) и заменить его на A x A (умноженный на себя A), если это быстрее. Ответ здесь - нет, компилятор на этапе оптимизации ничего не делает в зависимости от того, присутствуют ли скобки.

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

Позвольте мне сказать это осторожно. Если вы добавите в выражение круглые скобки, которые совершенно не нужны (никак не влияют на значение или порядок вычисления выражения), компилятор молча отбросит их и сгенерирует тот же код.

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

short int a = 30001, b = 30002, c = 30003;
int d = -a + b + c;    // ok
int d = (-a + b) + c;  // ok, same code
int d = (-a + b + c);  // ok, same code
int d = ((((-a + b)) + c));  // ok, same code
int d = -a + (b + c);  // undefined behaviour, different code

Поэтому добавьте скобки, если хотите, но убедитесь, что они действительно не нужны!

Я никогда не делаю. Существует риск ошибки без реальной выгоды.


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

david.pfx
источник
Неопределенное поведение в последней строке связано с тем, что подписанное короткое замыкание имеет только 15 бит после знака, поэтому максимальный размер 32767, верно? В этом тривиальном примере компилятор должен предупредить о переполнении, верно? +1 для встречного примера, в любом случае. Если бы они были параметрами функции, вы бы не получили предупреждение. Кроме того, если aдействительно может быть без знака, ведение вашего вычисления также -a + bможет легко переполниться, если бы оно aбыло отрицательным и bположительным.
Патрик М,
@PatrickM: см. Редактировать. Неопределенное поведение означает, что компилятор может делать то, что ему нравится, включая выдачу предупреждения или нет. Арифметика без знака не производит UB, но уменьшается по модулю на следующую более высокую степень двух.
david.pfx
Выражение (b+c)в последней строке будет выдвигать свои аргументы int, поэтому, если компилятор не определит int16-битный код (либо потому, что он древний, либо предназначается для небольшого микроконтроллера), последняя строка будет вполне допустимой.
суперкат
@supercat: я так не думаю. Общий тип и тип результата должен быть коротким int. Если это не то, что когда-либо задавали, возможно, вы хотели бы задать вопрос?
david.pfx
@ david.pfx: правила арифметического продвижения C довольно ясны: все, что меньше, чем intполучает повышение, intесли только этот тип не сможет представить все свои значения, и в этом случае он будет повышен unsigned int. Компиляторы могут пропустить рекламные акции, если все определенные варианты поведения будут такими же, как если бы рекламные акции были включены . На машине, где 16-битные типы ведут себя как оборачивающееся абстрактное алгебраическое кольцо, (a + b) + c и a + (b + c) будут эквивалентны. Однако, если бы intэто был 16-битный тип, захваченный переполнением, то были бы случаи, когда одно из выражений ...
суперкат
7

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

Итак, где вы и я могли бы увидеть ...

  • "int a = ((1 + 2) + 3);"

... компилятор может выдавать что-то вроде этого:

  • Char [1] :: "а"
  • Int32 :: DeclareStackVariable ()
  • Int32 :: 0x00000001
  • Int32 :: 0x00000002
  • Int32 :: Add ()
  • Int32 :: 0x00000003
  • Int32 :: Add ()
  • Int32 :: AssignToVariable ()
  • недействительными :: DiscardResult ()

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

Ладно, это совсем не то, с чем мы с тобой имеем дело, но тогда мы этим не занимаемся!

Фил В.
источник
4
Там нет ни одного компилятора C ++, который будет производить что-либо даже удаленно, как это. Обычно они вырабатывают реальный код процессора, и сборка тоже не выглядит так.
MSalters
3
цель здесь состояла в том, чтобы продемонстрировать разницу в структуре между написанным кодом и скомпилированным выводом. даже здесь большинство людей не смогут прочитать фактический машинный код или сборку
DHall
@MSalters Nitpicking clang выдаст «что-то в этом роде», если вы будете рассматривать LLVM ISA как «что-то в этом роде» (это SSA, не основанный на стеке). Учитывая, что можно написать бэкэнд JVM для LLVM, а JVM ISA (AFAIK) на основе стека clang-> llvm-> JVM будет выглядеть очень похоже.
Мачей Печотка
Я не думаю, что LLVM ISA может определять переменные стека имен, используя строковые литералы времени выполнения (только первые две инструкции). Это серьезно смешивает время выполнения и время компиляции. Различие имеет значение, потому что этот вопрос именно об этой путанице.
MSalters
6

Зависит, если это с плавающей точкой или нет:

  • В арифметике с плавающей запятой сложение не является ассоциативным, поэтому оптимизатор не может переупорядочить операции (если вы не добавите переключатель компилятора fastmath).

  • В целочисленных операциях они могут быть переупорядочены.

В вашем примере оба будут работать в одно и то же время, потому что они будут скомпилированы с точно таким же кодом (сложение оценивается слева направо).

однако даже Java и C # смогут оптимизировать его, они просто сделают это во время выполнения.

чокнутый урод
источник
+1 за то, что операции с плавающей запятой не ассоциативны.
Довал
В примере вопроса круглые скобки не изменяют ассоциативность по умолчанию (слева), поэтому этот момент спорный.
Марк ван Леувен
1
Насчет последнего предложения, я так не думаю. И в java a, и в c # компилятор выдаст оптимизированный байт-код / ​​IL. Время выполнения не влияет.
Стефано Алтьери
IL не работает с выражениями такого рода, инструкции берут определенное количество значений из стека и возвращают определенное количество значений (обычно 0 или 1) в стек. Говорить о том, что оптимизируется во время выполнения в C #, бессмысленно.
Джон Ханна
6

Типичный компилятор C ++ переводит в машинный код, а не в сам C ++ . Это удаляет бесполезные парены, да, потому что к тому времени, когда это сделано, паренов вообще нет. Машинный код не работает таким образом.

Ложка
источник
5

Оба кода в конечном итоге жестко закодированы как 6:

movl    $6, -4(%rbp)

Проверьте себя здесь

MonoThreaded
источник
2
Что это assembly.ynh.io штуковина?
Питер Мортенсен
Это псевдокомпилятор, который преобразует код C в сборку x86 онлайн
MonoThreaded
1

Нет, но да, но, может быть, но, может быть, по-другому, но нет.

Как уже указывали люди (если принять язык, в котором сложение является левоассоциативным, например C, C ++, C # или Java), выражение ((1 + 2) + 3)в точности эквивалентно 1 + 2 + 3. Это разные способы написания чего-либо в исходном коде, что будет иметь нулевой эффект на результирующий машинный код или байт-код.

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

Но есть кое-что еще к вашему вопросу.

В случае любого здравомыслящего компилятора C, C ++, Java или C # я ожидаю, что результат обоих приведенных вами утверждений будет иметь точно такие же результаты, как:

int a = 6;

Почему полученный код должен тратить время на математику литералов? Никакие изменения в состоянии программы не остановят результат от 1 + 2 + 3того 6, так это то, что должно идти в коде выполняются. В самом деле, может быть, даже не это (в зависимости от того, что вы делаете с этим 6, может быть, мы можем выбросить все это; и даже C # с его философией «не оптимизировать сильно, поскольку джиттер оптимизирует это в любом случае») либо произведет эквивалент int a = 6или просто выбросить все как ненужное).

Это, однако, приводит нас к возможному расширению вашего вопроса. Учтите следующее:

int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;

и

int c;
if(d < 100)
  c = 0;
else
  c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);

(Обратите внимание, что последние два примера не являются допустимыми в C #, в то время как все остальное здесь и они действительны в C, C ++ и Java.)

Здесь мы снова имеем абсолютно эквивалентный код с точки зрения вывода. Поскольку они не являются константными выражениями, они не будут вычисляться во время компиляции. Возможно, что одна форма быстрее, чем другая. Что быстрее? Это будет зависеть от процессора и, возможно, от некоторых довольно произвольных различий в состоянии (особенно потому, что если он быстрее, он вряд ли будет намного быстрее).

И они не совсем не связаны с вашим вопросом, поскольку они в основном касаются различий в порядке, в котором что-то концептуально делается.

В каждом из них есть причина подозревать, что один может быть быстрее другого. Одиночные декременты могут иметь специальную инструкцию, поэтому (b / 2)--могут быть быстрее (b - 2) / 2. d * 32возможно, будет произведен быстрее, превратив его в d << 5так, чтобы сделать d * 32 - dбыстрее, чем d * 31. Различия между двумя последними особенно интересны; одна позволяет пропустить некоторую обработку в некоторых случаях, а другая исключает возможность неправильного прогнозирования ветвлений.

Таким образом, это оставляет нам два вопроса: 1. Является ли один на самом деле быстрее, чем другой? 2. Будет ли компилятор преобразовывать медленный в быстрый?

И ответ 1. Это зависит. 2. Возможно.

Или расширить, это зависит, потому что это зависит от рассматриваемого процессора. Конечно, существуют процессоры, в которых простой машинный код, эквивалентный одному, будет быстрее, чем простой машинный код, эквивалентный другому. На протяжении всей истории электронных вычислений не было ни одного, который всегда был быстрее, либо (элемент неправильного предсказания ветвления, в частности, не относился ко многим, когда неконвейерные ЦП были более распространенными).

И, возможно, поскольку существует множество различных оптимизаций, которые будут выполнять компиляторы (и jitters, и обработчики сценариев), и хотя некоторые из них могут быть обязательными в определенных случаях, мы, как правило, сможем найти некоторые части логически эквивалентного кода, которые даже самый наивный компилятор имеет точно такие же результаты и некоторые фрагменты логически эквивалентного кода, где даже самый сложный код производит более быстрый код для одного, чем для другого (даже если нам нужно написать что-то совершенно патологическое, чтобы доказать свою точку зрения).

Это может показаться очень маленькой проблемой оптимизации,

Нет. Даже с более сложными различиями, чем те, которые я здесь привожу, это кажется абсолютно незначительным беспокойством, которое не имеет ничего общего с оптимизацией. Во всяком случае, это вопрос пессимизации, так как вы подозреваете, что труднее читать ((1 + 2) + 3может быть медленнее, чем легче читать 1 + 2 + 3.

но выбор C ++ вместо C # / Java / ... все об оптимизации (IMHO).

Если это действительно то, что выбор C ++ вместо C # или Java был «всем», я бы сказал, что люди должны записать свои копии Stroustrup и ISO / IEC 14882 и освободить пространство своего компилятора C ++, чтобы оставить место для еще нескольких MP3 или чего-то еще.

Эти языки имеют разные преимущества по сравнению друг с другом.

Одним из них является то, что C ++ по-прежнему быстрее и легче в использовании памяти. Да, есть примеры, когда C # и / или Java работают быстрее и / или лучше используют память на протяжении всего жизненного цикла приложения, и они становятся все более распространенными по мере совершенствования соответствующих технологий, но мы все же можем ожидать, что средняя программа, написанная на C ++, будет меньший исполняемый файл, который выполняет свою работу быстрее и использует меньше памяти, чем эквивалент на любом из этих двух языков.

Это не оптимизация.

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

Правильное слово для «ускорения» - это не оптимизация . Правильное слово здесь - улучшение . Если вы вносите изменения в программу, и единственное значимое отличие состоит в том, что она теперь быстрее, она никак не оптимизирована, она просто лучше.

Оптимизация - это когда мы делаем улучшения в отношении конкретного аспекта и / или конкретного случая. Типичные примеры:

  1. Теперь это быстрее для одного варианта использования, но медленнее для другого.
  2. Это теперь быстрее, но использует больше памяти.
  3. Теперь память легче, но медленнее.
  4. Теперь быстрее, но сложнее в обслуживании.
  5. Теперь легче поддерживать, но медленнее.

Такие случаи были бы оправданы, если, например:

  1. Более быстрый вариант использования более распространен или более затруднен для начала.
  2. Программа была неприемлемо медленной, и у нас много свободной оперативной памяти.
  3. Программа остановилась из-за того, что использовала столько оперативной памяти, что тратила на нее больше времени, чем на суперскоростную обработку.
  4. Программа была неприемлемо медленной, а сложный для понимания код хорошо документирован и относительно стабилен.
  5. Программа по-прежнему приемлемо быстра, а более понятная кодовая база дешевле в обслуживании и позволяет быстрее вносить другие улучшения.

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

И выбор языка здесь имеет значение, потому что он может повлиять на скорость, использование памяти и читаемость, но также совместимость с другими системами, доступность библиотек, доступность сред выполнения, зрелость этих сред выполнения в данной операционной системе. (что касается моих грехов, то я почему-то выбрал Linux и Android в качестве своих любимых ОС и C # в качестве моего любимого языка, и хотя Mono великолепен, но я все еще сталкиваюсь с этим совсем немного).

Сказать «выбор C ++ вместо C # / Java / ... это все об оптимизации» имеет смысл, только если вы думаете, что C ++ действительно отстой, потому что оптимизация о «лучше, несмотря на ...», а не «лучше». Если вы думаете, что C ++ лучше, несмотря на саму себя, то последнее, что вам нужно, это беспокоиться о таких мелких микрооптах. В самом деле, вам, вероятно, лучше вообще отказаться от него; счастливые хакеры - это тоже качество для оптимизации!

Если, однако, вы склонны сказать «я люблю C ++, и одна из вещей, которые мне нравятся, это выдавливать лишние циклы», то это другое дело. Это все еще тот случай, когда микроопты того стоят, только если они могут быть рефлексивной привычкой (то есть способ, которым вы склонны кодировать естественным путем, будет быстрее, чем медленнее). В противном случае это даже не преждевременная оптимизация, это преждевременная пессимизация, которая только усугубляет ситуацию.

Джон Ханна
источник
0

Круглые скобки указывают компилятору, в каком порядке следует вычислять выражения порядка. Иногда они бесполезны (за исключением того, что они улучшают или ухудшают читаемость), потому что они определяют порядок, который будет использоваться в любом случае. Иногда они меняют порядок. В

int a = 1 + 2 + 3;

Практически на каждом существующем языке существует правило, согласно которому сумма оценивается путем добавления 1 + 2, а затем добавления результата плюс 3. Если вы написали

int a = 1 + (2 + 3);

тогда в скобках будет указан другой порядок: сначала добавьте 2 + 3, затем добавьте 1 плюс результат. Ваш пример скобок производит тот же порядок, который был бы произведен в любом случае. Теперь в этом примере порядок операций немного отличается, но способ целочисленного сложения работает, результат тот же. В

int a = 10 - (5 - 4);

круглые скобки имеют решающее значение; если оставить их, результат изменится с 9 на 1.

После того, как компилятор определил, какие операции выполняются в каком порядке, скобки полностью забываются. Все, что запоминает компилятор на этом этапе, - какие операции и в каком порядке выполнять. Так что на самом деле компилятор не может оптимизировать ничего, скобки исчезли .

gnasher729
источник
practically every language in existence; кроме APL: Попробуйте (здесь) [tryapl.org] ввести (1-2)+3(2), 1-(2+3)(-4) и 1-2+3(также -4).
Томсминг
0

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

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

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

jinzai
источник
1
Я не уверен, что это отвечает на вопрос. Поддерживающие пункты являются более запутанными, чем полезными. Какое значение имеет компилятор Java для компилятора C ++?
Адам Цукерман
ОП спросил, нет ли скобок ... Я ответил, что они были, и объяснил, что исполняемый код - это просто числа, представляющие коды операций. Ява была воспитана в другом ответе. Я думаю, что это хорошо отвечает на вопрос ... но это только мое мнение. Спасибо за ответ.
дзиндзай
3
Круглые скобки не заставляют "порядок операции". Они меняют приоритет. Так, в a = f() + (g() + h());компилятор волен называть f, gи hв таком порядке (или в любом порядке , это радует).
Алок
У меня есть некоторое несогласие с этим утверждением ... вы можете привести порядок операций в скобки.
Джинзай
0

Компиляторы, независимо от языка, переводят всю инфиксную математику в постфиксную. Другими словами, когда компилятор видит что-то вроде:

((a+b)+c)

это переводит это в это:

 a b + c +

Это сделано потому, что в то время как инфиксная нотация легче читается людьми, постфиксная нотация намного ближе к фактическим шагам, которые компьютер должен предпринять, чтобы выполнить работу (и потому что для этого уже есть хорошо разработанный алгоритм). По определению, postfix устраняет все проблемы с порядком операций или круглыми скобками, что, естественно, значительно упрощает процесс написания машинного кода.

Я рекомендую статью в Википедии об обратной польской записи для получения дополнительной информации по этому вопросу.

Брайан Дрозд
источник
5
Это неверное предположение о том, как компиляторы переводят операции. Вы, например, предполагаете, что здесь машина стека. Что если бы у вас был векторный процессор? Что делать, если у вас есть машина с большим количеством регистров?
Ахмед Масуд