Чи компілятор 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 ви ніколи не знаєте, коли компілятор запуститься, і спробуйте почати компілювати код. Але я зауважу, що дострокова компіляція до нативного коду не взаємовиключна зі збиранням сміття, див., Наприклад, Standard ML та D. І я бачив переконливі аргументи, що збирання сміття є більш ефективним ...
Doval

7
... ніж RAII та розумні покажчики, тож це більше питання дотримання добре перебитого шляху (C ++) проти відносно непрочитаного шляху виконання ігрового програмування на цих мовах. Я також зауважу, що турбуватися про дужки є божевільним - я бачу, звідки ви беретеся, але це смішна мікрооптимізація. Вибір структур даних та алгоритмів у вашій програмі безумовно буде домінувати в продуктивності, а не таких дрібницях.
Doval

6
Гм ... Якої оптимізації ви точно очікуєте? Якщо ви говорите про статичний аналіз, то в більшості мов, які я знаю, це буде замінено статично відомим результатом (реалізація на основі LLVM навіть застосовує це, AFAIK). Якщо ви говорите про порядок виконання, це не має значення, оскільки це та сама операція і без побічних ефектів. Для додавання в будь-якому випадку потрібні два операнди. І якщо ви використовуєте це для порівняння C ++, Java та C # щодо продуктивності, це здається, що ви не маєте чіткого уявлення про те, що таке оптимізація та як вони працюють, тому слід зосередитись на тому, щоб навчитися цьому.
Теодорос Чатзіґананакіс

5
Мені цікаво, чому а) ви вважаєте вираз, викладений в дужках, більш читабельним (для мене це просто виглядає некрасивим, оманливим (чому вони підкреслюють саме цей порядок? Чи не повинен він тут бути асистентним?) І незграбно) б) чому б ви думали без в круглих дужках це може бути краще (чітко розбирати паролі для машини легше, ніж міркувати про виправлення операторів. Як каже Марк ван Левен, це абсолютно не впливає на час виконання).
приблизно

Відповіді:


87

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

Якщо ви не пишете жодних круглих дужок, то аналізатор все-таки повинен виконувати свою роботу, створюючи дерево розбору, а правила пріоритетності та асоціативності оператора вказують, яке саме дерево розбору воно має побудувати. Ви можете вважати, що ці правила вказують компілятору, які (неявні) дужки він повинен вставити у ваш код, хоча аналізатор насправді ніколи не має справу з дужками в цьому випадку: він просто був побудований для створення того ж дерева розбору, як якщо б дужки були присутні в певних місцях. Якщо розмістити дужки в тих місцях, як в int a = (1+2)+3;(асоціативність +зліва), то аналізатор прийде до того ж результату трохи іншим маршрутом. Якщо ви вводите різні дужки, як уint a = 1+(2+3);тоді ви змушуєте інше дерево розбору, що, можливо, спричинить генерування різного коду (хоча, можливо, і ні, оскільки компілятор може застосовувати перетворення після побудови дерева розбору, доки ефект від виконання результуючого коду ніколи не буде відрізнятися для це). Припустимо, що існує різниця в коді зміни розміру, нічого загального не можна сказати щодо того, який є більш ефективним; Найважливішим моментом є звичайно те, що більшість часу дерева розбору не дають математично еквівалентних виразів, тому порівняння швидкості їх виконання знаходиться поруч із точкою: треба просто написати вираз, який дає належний результат.

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

І жодне з цього не має нічого спільного з оптимізацією , яка виникає після побудови дерева розбору, тому воно не може знати, як було побудовано дерево розбору. Це стосується без змін: від найдавніших і найдурніших компіляторів до найрозумніших і найсучасніших. Лише в інтерпретованій мові (де "час компіляції" та "час виконання" збігаються) могла б бути покарання за надмірні дужки, але навіть тоді я думаю, що більшість таких мов організовані так, що принаймні етап розбору виконується лише один раз для кожного оператора (зберігання деякої попередньо розібраної форми для виконання).


s / oldes / найстаріший /. Гарна відповідь, +1.
Девід Конрад

23
Загальне попередження про нитку: "Питання не дуже добре поставлене". - Я не погоджуюся, вважаючи, що "чітко кажучи" означає "чітко підказуючи, який прогалину знань бажає заповнити". По суті питання полягає в «оптимізації для X, вибору A або B, і чому? Що відбувається під ним?», Що, принаймні, мені дуже чітко підказує, що таке розрив знань. Недолік у питанні, на яке ви справедливо вказуєте та дуже добре вирішуєте, полягає в тому, що він побудований на несправній ментальній моделі.
Йонас Келькер

Для отримання a = b + c * d;, a = b + (c * d);було б [нешкідливі] надлишковими круглі дужки. Якщо вони допоможуть вам зробити код більш читабельним, добре. a = (b + c) * d;це були б зайві дужки - вони фактично змінюють отримане дерево розбору та дають інший результат. Цілком законно робити (насправді, необхідно), але вони не є такими, як мається на увазі групування за замовчуванням.
Філ Перрі

1
@ OrangeDog правда, ганьба коментар Бен виявив кілька людей, які люблять претендувати на VM швидше, ніж рідні.
gbjbaanb

1
@ JonasKölker: Моє вступне речення насправді стосується питання, сформульованого в заголовку: реально не можна відповісти на запитання про те, чи компілятор вставляє чи видаляє круглі дужки, оскільки це засноване на помилковому уявленні про те, як працюють компілятори. Але я згоден, цілком зрозуміло, який прогалину знань потрібно подолати.
Марк ван Левен

46

Дужки існують виключно для вашої користі, а не компіляторів. Компілятор створить правильний машинний код для представлення вашої заяви.

FYI, компілятор досить розумний, щоб повністю його оптимізувати, якщо зможе. У ваших прикладах це перетвориться int a = 6;на час компіляції.


9
Абсолютно - вставте стільки круглих дужок, скільки вам подобається, і дозвольте компілятору зробити важку роботу, щоб з'ясувати, що ви хочете :)
gbjbaanb

23
@Serge справжнє програмування - це скоріше про читабельність вашого коду, ніж про продуктивність. Ви будете ненавидіти себе наступного року, коли вам доведеться налагоджувати аварію, і у вас є лише "оптимізований" код, який потрібно пройти.
храповик виродка

1
@ratchetfreak, ти маєш рацію, але я також знаю, як прокоментувати свій код. int a = 6; // = (1 + 2) + 3
Серж

20
@ Серж, я знаю, що не витримає після року перетворень, вчасно коментарі та код вийдуть із синхронізації, і тоді ви закінчитесьint a = 8;// = 2*3 + 5
храповик виродка

21
або з www.thedailywtf.com:int five = 7; //HR made us change this to six...
Mooing Duck

23

Відповідь на запитання, яке ви насправді задали, - ні, але відповідь на питання, яке ви мали намір задати, - так. Додавання дужок не уповільнює код.

Ви задали питання щодо оптимізації, але дужки не мають нічого спільного з оптимізацією. Компілятор застосовує різні методи оптимізації з метою поліпшення або розміру, або швидкості згенерованого коду (іноді і того й іншого). Наприклад, він може взяти вираз A ^ 2 (A квадрат) і замінити його на 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. Це складна тема, поза межами цієї відповіді.


Невизначена поведінка в останньому рядку полягає в тому, що підписаний короткий має лише 15 біт після знаку, тож розмір максимуму 32767, правда? У цьому тривіальному прикладі компілятор повинен попереджати про переповнення, правда? +1 для зустрічного прикладу, в будь-якому випадку. Якби вони були параметрами функції, ви не отримали би попередження. Крім того, якщо aце справді не може бути підписано, провідний ваш розрахунок також -a + bможе легко переповнитися, якщо aвони будуть негативними та bпозитивними.
Патрік М

@PatrickM: див. Редагування. Невизначена поведінка означає, що компілятор може робити те, що йому подобається, в тому числі видавати попередження, чи ні. Непідписана арифметика не дає UB, але зменшується по модулю наступною більшою потужністю в два.
david.pfx

Вираз (b+c)в останньому рядку сприятиме його аргументам int, тому, якщо компілятор не визначить int16 біт (або тому, що це стародавнє, або націлений на невеликий мікроконтролер), останній рядок був би абсолютно законним.
supercat

@supercat: Я не думаю, що так. Загальний тип і тип результату повинні бути короткими. Якщо це не те, що коли-небудь задавали, можливо, ви хотіли б написати питання?
david.pfx

@ david.pfx: Правила арифметичної реклами на C досить чіткі: все, що менше, ніж intпросувається до intтих пір, якщо цей тип не зміг би представити всі його значення, і в цьому випадку він отримує підвищення unsigned int. Компілятори можуть пропустити рекламні кампанії, якщо всі визначені форми поведінки будуть однаковими, як якщо б рекламні кампанії були включені . На машині, де 16-бітні типи ведуть себе як обгорткове абстрактне алгебраїчне кільце, (a + b) + c і a + (b + c) будуть еквівалентними. Якщо б intбув 16-розрядний тип, який потрапляв у переповнення, тоді були б випадки, коли один із виразів ...
supercat

7

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

Отже, де ми з вами можемо побачити ...

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

... компілятор може видати щось подібне:

  • Char [1] :: "a"
  • Int32 :: DeclareStackVariable ()
  • Int32 :: 0x00000001
  • Int32 :: 0x00000002
  • Int32 :: Додати ()
  • Int32 :: 0x00000003
  • Int32 :: Додати ()
  • Int32 :: AssignToVariable ()
  • void :: DiscardResult ()

Програма виконується, починаючи з початку і виконуючи кожну інструкцію по черзі.
Операторський пріоритет зараз "першим, першим поданим".
Все сильно набрано, тому що компілятор все це випрацював , розриваючи оригінальний синтаксис.

Гаразд, це нічого подібне до речей, якими ми з вами займаємось, але тоді ми її не виконуємо!


4
Немає жодного компілятора C ++, який би створював що-небудь навіть віддалено, як це. Як правило, вони виробляють фактичний код процесора, і збірка також не виглядає таким чином.
MSalters

3
наміром тут було продемонструвати різницю в структурі між написаним кодом та складеним результатом. навіть тут більшість людей не зможуть прочитати фактичний код машини чи збірку
DHall

@MSalters Nitpicking clang видає "щось подібне", якщо ви трактуєте LLVM ISA як "щось подібне" (це SSA не на основі стека). З огляду на те, що можна написати резервний JVM для LLVM, а JVM ISA (AFAIK) - це стек на основі clang-> llvm-> JVM виглядав би дуже схожим.
Мацей П'єхотка

Я не думаю, що ISL LLVM має можливість визначати змінні стека імен за допомогою літеральних рядків виконання (лише перші дві інструкції). Це серйозно змішує час виконання та час компіляції. Відмінність має значення тому, що саме це питання стосується тієї плутанини.
MSalters

6

Залежить, плаваюча точка чи ні:

  • У арифметичному додаванні з плаваючою комою додавання не асоціативне, тому оптимізатор не може впорядкувати операції (якщо ви не додаєте компілятор Fastmath компілятора).

  • У цілих операціях вони можуть бути впорядковані.

У вашому прикладі обидва будуть працювати точно той же час, оскільки вони будуть компілюватися в точно той же код (додавання оцінюється зліва направо).

однак навіть Java та C # зможуть оптимізувати це, вони просто робитимуть це під час виконання.


+1 за те, що операції з плаваючою комою не асоціативні.
Doval

У прикладі запитання круглі дужки не змінюють асоціативність за замовчуванням (зліва), тому ця точка є суперечливою.
Марк ван Левен

1
Щодо останнього речення, я не думаю. І в java a, і в # компілятор буде виробляти оптимізований байт-код / ​​IL. Час виконання не впливає.
Стефано Альтьєрі

IL не працює на виразах подібного роду, вказівки беруть певну кількість значень із стека та повертають певну кількість значень (як правило, 0 або 1) у стек. Говорити про оптимізацію подібних речей під час виконання в C # - це нісенітниця.
Джон Ханна

6

Типовий компілятор C ++ перекладається на машинний код, а не сам C ++ . Це видаляє непотрібні пароні, так, тому що до моменту його створення немає зовсім. Машинний код не працює таким чином.



1

Ні, але так, але, можливо, але, можливо, інакше, але ні.

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

Так чи інакше, результат буде інструкцією, наприклад, додати два регістри, а потім додати третій, або взяти два значення зі стека, додати його, відсунути назад, потім взяти його та інше і додати їх, або додати три регістри в одна операція або якийсь інший спосіб підсумовувати три числа залежно від того, що є найбільш розумним на наступному рівні (машинний код або байт-код). У випадку з байт-кодом, це, в свою чергу, буде, ймовірно, зазнати подібної реструктуризації в тому, що, наприклад, еквівалент ІЛ цього (який би був серією завантажень у стек, і вискакуючи пари, щоб додати і потім відштовхнути результат) не призвело б до прямої копії цієї логіки на рівні машинного коду, але до чогось більш розумного для цієї машини.

Але у вашому питанні є щось більше.

У випадку будь-якого розумного компілятора 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. Можливо.

Або розширити, це залежить, оскільки це залежить від розглянутого процесора. Звичайно, існували процесори, де наївний машино-кодовий еквівалент одного був би швидшим, ніж наївний машино-кодовий еквівалент іншого. Протягом історії електронних обчислень не було жодного, який був завжди швидшим (зокрема, елемент помилкового прогнозування галузей не був актуальним для багатьох, коли більш нечастотні процесори були більш поширеними).

Можливо, тому, що існує маса різних оптимізацій, які будуть робити компілятори (і тремтіння, і сценарії), і хоча деякі можуть мати мандат у певних випадках, ми, як правило, зможемо знайти деякі фрагменти логічно еквівалентного коду, які навіть самий наївний компілятор має абсолютно однакові результати та деякі фрагменти логічно еквівалентного коду, де навіть найдосконаліший виробляє швидший код для одного, ніж для іншого (навіть якщо нам доведеться написати щось абсолютно патологічне, щоб довести свою думку).

Це може здатися дуже крихітним питанням оптимізації,

Ні. Навіть при більш складних відмінностях від тих, які я тут навожу, це здається абсолютно хвилюючим питанням, яке не має нічого спільного з оптимізацією. Якщо що, це питання песимізації, оскільки ви підозрюєте, що важче читати ((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.

Після того, як компілятор визначив, які операції виконуються в якому порядку, дужки повністю забуті. Все, що пам’ятає компілятор в цей момент, - це які операції виконувати в якому порядку. Тож насправді немає нічого, що компілятор міг би тут оптимізувати, дужки відсутні .


practically every language in existence; крім APL: Спробуйте (тут) [tryapl.org] ввівши (1-2)+3(2), 1-(2+3)(-4) та 1-2+3(також -4).
tomsmeding

0

Я погоджуюся з великою частиною сказаного, однак ... надмірна арка тут полягає в тому, що в дужках є примусовий порядок роботи ..., що компілятор абсолютно робить. Так, він виробляє машинний код ... але це не сенс і не те, що просять.

Дужки в дужках справді вже відсутні: як уже було сказано, вони не є частиною машинного коду, а це цифри, а не інше. Код складання не є машинним кодом, він читається напівлюдина та містить інструкції по імені - не опкод. Машина виконує так звані опкоди - числові представлення мови складання.

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


1
Я не впевнений, що це відповідає на питання. Підтверджуючі параграфи є більш заплутаними, ніж корисними. Як компілятор Java має відношення до компілятора C ++?
Адам Цукерман

ОП запитав, чи немає в дужках… Я сказав, що вони є, і далі пояснив, що виконуваний код - це просто числа, що представляють опкоди. Ява виховувався в іншій відповіді. Я думаю, що це відповідає на питання просто чудово ... але це лише моя думка. Дякуємо за відповідь.
jinzai

3
Дужки не примушують "порядок роботи". Вони змінюють пріоритет. Отже, a = f() + (g() + h());компілятор може дзвонити f, gі hв тому порядку (або в будь-якому порядку, який йому заманеться).
Алок

Я маю певну незгоду з цим твердженням ... ви абсолютно можете примусити порядок роботи з дужками.
jinzai

0

Компілятори, незалежно від мови, переводять усю математику інфіксів у постфікс. Іншими словами, коли компілятор бачить щось подібне:

((a+b)+c)

це перекладає це так:

 a b + c +

Це робиться тому, що, хоча нотацію інфіксації людям легше читати, нотація постфікса набагато наближається до дійсних кроків, які повинен здійснити комп'ютер (і тому, що для нього вже є добре розроблений алгоритм.) Визначення, постфікс усуває всі проблеми з порядком виконання операцій чи круглими дужками, що, природно, значно полегшує ситуацію при написанні машинного коду.

Я рекомендую статтю у Вікіпедії про зворотну польську нотацію для отримання додаткової інформації з цього питання.


5
Це неправильне припущення про те, як компілятори переводять операції. Ви, наприклад, тут припускаєте машину для складання. Що робити, якщо у вас був векторний процесор? що робити, якщо у вас була машина з великою кількістю регістрів?
Ахмед Масуд
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.