Чому ця версія логічного І на С не відображає поведінку короткого замикання?


80

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

У C:

Мені здається, що у aхибному випадку програма взагалі не намагатиметься оцінити b, але я повинен помилятися. Чому програма взагалі торкається bв цьому випадку, коли цього не потрібно?


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

49
@RobertHarvey Ви будете постійно бачити саме такий код у виробничих системах! Навряд чи це буде функція з іменем AND(), але функції, які отримують аргументи за значенням, а потім обчислює їх (чи ні) залежно від логіки функції, є скрізь. Незважаючи на те, що це "хитромудре питання", це критична поведінка С, яку потрібно зрозуміти. Мовою, де називають поіменне ім’я, це запитання мало б зовсім іншу відповідь.
Бен Джексон,

7
@BenJackson: Я коментував код, а не поведінку. Так, потрібно розуміти поведінку. Ні, вам не потрібно писати такий код.
Роберт Харві,

3
Це насправді досить актуально, якщо вам коли-небудь доведеться кодувати у VB і стикатися з IIf. Оскільки це функція, а не оператор, оцінка не замикається. Це може створити проблеми для розробників, звиклих до оператора короткого замикання, який потім пише подібні речі IIf(x Is Nothing, Default, x.SomeValue).
Dan Bryant,

3
@alk, тому що це звинувачення в системі освіти.
Shivan Dragon

Відповіді:


118

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

ОНОВЛЕННЯ

Що стосується того, чому я назвав це "фокусним запитанням": це через відсутність чітко визначеного контексту, де слід розглядати "коротке замикання" (принаймні, як відтворено ОП). Багато людей, отримуючи лише визначення функції, припускають, що контекст питання задає питання про суть функції ; вони часто не розглядають функцію як вираз сам по собі. Це «фокус» питання; Щоб нагадати вам, що в програмуванні загалом, але особливо в таких мовах, як C-like, які часто мають багато винятків із правил, цього робити не можна. Приклад, якщо запитання було задано як таке:

Розглянемо наступний код. Sc_and покаже поведінку короткого замикання при виклику з main :

Було б відразу зрозуміло , що ви повинні думати про sc_andякість оператора в і самому по собі в своєму власному предметно-орієнтованої мови , і оцінках , якщо виклик sc_andекспонати короткого замикання поведінки як звичайний &&буде . Я б не вважав це питанням хитрості взагалі, тому що зрозуміло, що ви не повинні зосереджуватися на потрійному операторі, а натомість повинні зосередитися на механіці викликів функцій C / C ++ (і, я гадаю, привести добре в наступне запитання, щоб написати sc_andкоротке замикання, яке передбачає використання, #defineа не функції).

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


13
Трійковий оператор робить коротке замикання? Ні. Він оцінює один із виразів, наступних за ним ?, як і в if (condition) {when true} else {when-false}. Це не називається коротким замиканням.
Йенс


17
@Jens: Я б назвав будь-який випадок, коли оператор пропускає оцінку одного або кількох своїх операндів, формою "короткого замикання", але це насправді лише питання того, як ви вирішите визначити термін.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

13
@Jens Це синтаксичний цукор для if else, а не для if. І навіть тоді це не так; ?:оператор є виразом , if-elseє твердження . Це семантично дуже різні речі, навіть якщо ви можете побудувати еквівалентний набір тверджень, які дають той самий ефект, що і трійковий вираз . Ось чому він вважається короткозамкненим; більшість інших виразів завжди оцінюють усі їх операнди ( +,-,*,/тощо). Коли вони цього не роблять, це коротке замикання ( &&, ||).
aruisdante

9
Так, для мене важливою відмінністю є те, що ми говоримо про операторів. Звичайно, оператори управління потоком контролюють, який шлях коду виконується. Для операторів не очевидно для тих, хто не знайомий з мовами похідних C та C, що оператор може не оцінювати всі свої операнди, тому важливо мати термін для розмови про цю властивість, і для цього я використовую "короткий контурна ".
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

43

Коли заява

виконується, b++не буде обчислюватися, якщо операнд aоцінений false(поведінка короткого замикання). Це означає, що побічний ефект bне відбудеться.

Тепер погляньмо на функцію:

і зателефонуйте цьому

У цьому випадку, aє trueчи false, b++завжди буде оцінюватися 1 під час виклику функції, і побічний ефект на bзавжди матиме місце.

Те саме стосується і

і


1 Порядок оцінки аргументів функції не вказаний.


1
Добре, так співпрацюємо з відповіддю Стівена Куана: чи можливо (юридично), щоб компілятор міг вбудувати функцію "and_fun", наприклад, коли ви викликаєте "bool x = and_fun (a, b ++);" b ++ не буде збільшено, якщо a відповідає істині?
Shivan Dragon

4
@ShivanDragon Це для мене звучить як зміна поведінки, що спостерігається.
sapi

3
@ShivanDragon: При вбудуванні компілятора поведінка не зміниться. Це не так просто, як підмінити тіло функції.
Джек Едлі,

Для наочності ви можете додати int x = a ? b++ : 0, як коротке замикання, що спостерігається.
Пол Дрейпер,

@PaulDraper; Я зберіг оригінальний фрагмент, оскільки його читачі не плутають.
хакі

19

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

З іншого боку, це

б «коротке замикання», так як це макро не означає виклик функції і з цим немає оцінок аргументу функції , (S) виконуються.


може пояснити чому? Це виглядає для мене точно так само, як фрагмент опери. За винятком вбудованих, це інша сфера застосування.
dhein

5

Відредаговано для виправлення помилок, зазначених у коментарі @cmasters.


В

... returnвираз ed відображає оцінку короткого замикання, але виклик функції - ні.

Спробуйте зателефонувати

Виклик функції обчислює 1 / 0, хоча він ніколи не використовується, отже, викликаючи - ймовірно - помилку ділення на нуль.

Відповідними витягами із (проекту) стандарту ANSI C є:

2.1.2.3 Виконання програми

...

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

і

3.3.2.2 Виклики функцій

....

Семантика

...

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

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

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

  • Чи дає оцінка 1 / 0невизначену поведінку?
  • Список аргументів - це вираз? (Я думаю, що не)

PS

Навіть якщо ви перейдете на C ++ і визначите sc_andяк inlineфункцію, ви не отримаєте SCE. Якщо ви визначите його як макрос C, як це робить @alk , ви, звичайно, зробите.


1
Ні, inlineфункція не змінює семантику виклику функції. І та Семантика вказати , що всі аргументи будуть оцінені, як ви правильно процитовані. Навіть якщо компілятор може оптимізувати виклик, видима поведінка не повинна змінюватися, тобто sc_and(f(), g())повинна поводитися так, ніби обидва f()і g()завжди викликаються. sc_and(0, 1/0)поганий приклад , тому що це невизначене поведінку, і компілятор не зобов'язаний навіть назвати sc_and()...
cmaster - відновимо моника

@cmaster Дякую. Я просто не знав, що C ++ inlineзберігає семантику викликів. Я затримався з нульовим розділенням, як це підходить для прикладу, і SCE, я думаю, часто використовується, щоб уникнути невизначеної поведінки.
Ескіз

4

Щоб чітко побачити тернарне коротке замикання, спробуйте трохи змінити код, використовуючи вказівники на функції замість цілих чисел:

А потім скомпілюйте його (навіть майже без оптимізації -O0!). Ви побачите b(), що не виконується, якщо a()повертає false.

Тут згенерована збірка виглядає так:

Отож, як інші правильно вказали на фокус із питанням, змусити вас зосередитись на поведінці тернарного оператора, який РОЗВІДАЄ коротке замикання (виклик, що називається «умовною оцінкою»), а не на оцінку параметрів під час виклику (виклик за значенням), який НЕ робить короткого замикання.


0

C-потрійний оператор ніколи не може здійснити коротке замикання, оскільки він обчислює лише один вираз a (умова), щоб визначити значення, задане виразами b і c , якщо якесь значення може бути повернуте.

Наступний код:

Це майже еквівалентно наступному коду:

Вираз a може бути сформований іншими операторами, такими як && або || це може призвести до короткого замикання, оскільки вони можуть обчислити два вирази перед тим, як повернути значення, але це не буде розглядатися як тернарний оператор, що робить коротке замикання, а оператори, що використовуються в умові, як це робиться у звичайному операторі if.

Оновлення:

Існують певні суперечки щодо тернарного оператора як оператора короткого замикання. Аргумент говорить, що будь-який оператор, який не оцінює всі свої операнди, робить коротке замикання відповідно до @aruisdante у коментарі нижче. Якби дано це визначення, тоді тернарний оператор мав би коротке замикання, і у випадку, якщо це оригінальне визначення, я згоден. Проблема полягає в тому, що термін "коротке замикання" спочатку використовувався для певного типу оператора, який допускав таку поведінку, а це логічні / логічні оператори, і тому я спробую пояснити причину, чому саме такі.

Після статті Оцінка короткого замикання, оцінка короткого замикання стосується лише булевих операторів, реалізованих у мові таким чином, що, знаючи, що перший операнд зробить другий неактуальним, це означає, що оператор && є першим операндом помилковим , а для || Оператор є першим операндом true , специфікація C11 також зазначає це в 6.5.13 Оператор логічного І та 6.5.14 Оператор логічного АБО.

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

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

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


3
І чому голос проти? Я хотів би коментарів, щоб я міг виправити все, що я сказав неправильно. Будь ласка, будьте конструктивними.
Адріан Перес

2
Можливо, -1, бо це не 100% у темі. але, як би там не було, я дав +1, оскільки ваша відповідь принаймні пояснює її стійкість, і це, здається, не є неправильним ompv.
dhein

4
@AdrianPerez див. Розділ коментарів у моїй відповіді, чому більшість людей вважають, що 100% вважає, що оператор термінального зв'язку замикається. Це вираз, який не оцінює всі його операнди на основі вартості деяких його операндів. Це коротке замикання.
aruisdante

3
Я не можу придумати жодного не-булевого оператора, який не оцінює всі свої операнди. Але це окрім суті; Причиною того, що це називається замиканням, є прецизлі, оскільки це оператор (або в загальному розумінні вираз, а не вираз), який не оцінює всі свої операнди. Тип оператора насправді не має значення. Було б цілком можливо написати логічні оператори, які не мали короткого замикання, і ви могли б написати не логічні оператори, які також це зробили (наприклад: ціле число муліплікатонів 0 завжди дорівнює 0, тому негайно поверніть 0, якщо перший операнд 0).
aruisdante

2
x = a ? b : 0;семантично те саме, що(a && (x = b)) || (x = 0);
jxh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.