+ (+ k--) вираз у С


9

Я бачив це питання в тесті, в якому ми повинні сказати вихід наступного коду.

#include<stdio.h>

int main(){
    int k = 0;
    while(+(+k--)!=0)
    k=k++;
    printf("%d\n", k);  
    return 0;
}

Вихід є -1. Я не впевнений, чому це відповідь.

Що означає вираз +(+k--)у С?


4
Це було відформатовано так? Це означає ;-)
— Пітер -

3
Підказка: ніколи не пишіть BS-код таким у реальному житті.
— Jabberwocky

5
Це не УБ. Дивіться мою відповідь.
— dbush

@ Peter-ReinstateMonica Так, це було відформатовано так.
— Анкур Гаутам

2
Через його надуманий характер та химерні повороти - k=k++це не визначено, але це не визначено, тому що він ніколи не виконується через затуманену умову - я голосую за закриття як дублікат цього питання .
— Стів Саміт

Відповіді:


10

[Для запису я змінив цю відповідь досить суттєво, оскільки її прийняли і проголосували далі. Але це все ще говорить однаково.]

Цей код є глибоко, можливо навмисно, заплутаним. Він містить вузьковідхилений екземпляр жахливої невизначеної поведінки . В основному неможливо визначити, чи людина, яка побудувала це питання, була дуже, дуже розумною чи дуже-дуже дурною. І "урок", який цей код може передбачити, щоб навчити або перевірити вас про те, а саме - що оператор unary plus не робить багато - безумовно, недостатньо важливий, щоб заслужити такий вид підривної помилки.

Є два заплутані аспекти коду, дивна умова:

while(+(+k--)!=0)

і дементована заява, яку вона контролює:

k=k++;

Я спершу висвітлюю другу частину.

Якщо у вас є така змінна, kяку ви хочете збільшити на 1, C дає вам не один, не два, не три, а чотири різних способи зробити це:

  1. k = k + 1
  2. k += 1
  3. ++k
  4. k++

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

k = k++;

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

Тепер, якщо ви подивитеся дуже уважно, ви побачите, що в цій конкретній програмі рядок k = k++насправді не виконується, тому що (як ми вже побачимо) умова контролю спочатку не відповідає, тому цикл працює 0 разів . Отже, ця конкретна програма насправді не може бути визначеною - але вона все ще патологічно заплутана.

Дивіться також ці канонічні відповіді на всі запитання щодо невизначеної поведінки подібного роду.

Але ви не питали про k=k++частину. Ви запитали про першу заплутану частину, про +(+k--)!=0умову. Це виглядає дивно, тому що це дивно. Ніхто ніколи і ніколи не писав такого коду в справжній програмі. Тож немає причин вчитися розуміти це. (Так, її правда, вивчення меж системи може допомогти вам дізнатися про її точні моменти, але в моїй книзі є досить чітка грань між творчими роздумами, що провокують думки, проти грубих, образливих досліджень, і це вираження дуже чітко неправильна сторона цього рядка.)

У будь-якому випадку давайте вивчимо +(+k--)!=0. (А зробивши це, давайте забудемо все про це.) Будь-який вираз, подібний цьому, повинен бути зрозумілий зсередини. Я припускаю, ви знаєте що

k--

робить. Він приймає kпоточне значення і "повертає" його до решти виразу, і воно більш-менш одночасно зменшує k, тобто зберігає кількість k-1назад у k.

Але що тоді +робити? Це одинарний плюс, а не двійковий плюс. Це просто як одинарний мінус. Ви знаєте, що двійковий мінус робить віднімання: вираз

a - b

віднімає b від a. І ви знаєте, що одинарний мінус заперечує речі: вираз

-a

дає вам мінус a. Те, що +робить Унарі , це ... в основному нічого. +aдає вам aзначення, змінивши позитивні значення на позитивні та негативні значення на негативні. Отже вираз

+k--

дає тобі все k--, що тобі дали, тобто kстаре значення.

Але ми не закінчили, бо маємо

+(+k--)

Це просто бере все, що +k--вам подарувало, і знову застосовується +до нього неоднаково. Таким чином, він дає вам все +k--, що вам дав, що все k--, що вам дав, що було kстарим значенням.

Отже, врешті-решт, умова

while(+(+k--)!=0)

робить точно так само, як і набагато звичайніший стан

while(k-- != 0)

зробив би. (Це також робить те саме, що while(+(+(+(+k--)))!=0)було б зробити ще складнішим на вигляд умовою . І ці круглі дужки насправді не потрібні; це також робить те саме, що while(+ + + +k--!=0)було б зроблено.)

Навіть з'ясовуючи, що таке "нормальний" стан

while(k-- != 0)

робить - це щось хитро. У цьому циклі відбуваються такі дві речі: Оскільки цикл потенційно працює кілька разів, ми збираємось:

  1. продовжуйте робити k--, щоб робити все kменше і менше, але також
  2. продовжуйте робити тіло петлі, що б це не робило.

Але ми виконуємо цю k--частину одразу, перш ніж (або в процесі) вирішити, чи робити іншу поїздку через цикл. І пам'ятайте, що k--"повертає" старе значення k, перш ніж декламентувати його. У цій програмі початкове значення kдорівнює 0. Отже k--, буде повернути старе значення 0, а потім оновити kдо -1. Але тоді решта умови != 0- але, як ми щойно побачили, ми вперше перевірили умову, отримали 0. Так що ми не будемо робити жодних поїздок через цикл, тому не будемо намагатися виконати проблемне твердження k=k++взагалі.

Іншими словами, в цьому конкретному циклі, хоча я сказав, що "відбуваються такі дві речі", виявляється, що річ 1 відбувається один раз, а річ 2 відбувається нульовий раз.

У будь-якому випадку, я сподіваюся, що зараз достатньо зрозуміло, чому цей поганий привід для програми закінчується друком -1 як кінцевим значенням k. Зазвичай я не люблю відповідати на такі питання вікторини - це схоже на обман - але в цьому випадку, оскільки я так голосно не згоден з усією сутністю вправи, я не заперечую.


1
Спростовані приклади характерні для будь-якого основоположного класу. Як ще можна написати лаконічне запитання щодо переваги оператора на іспиті? Я викладаю математику, і якби у мене був нікель кожного разу, коли студент запитував "Коли я буду робити це в реальному житті?" ...
— Скотт

4
@Scott Там надумано, а потім надумано . Припустимо, ви інструктор з підготовки водіїв. Припустимо, ви попросите свого учня проїхати через інтенсивний рух у центрі міста, одночасно б'ючи його по голові бейсбольною битою. Можливо, студент засвоїть потенційно корисну майстерність. Але я називаю це зловживанням. І я притримуюсь своєї думки, що код у цьому питанні є образливим, має негативне педагогічне значення.
— Стів Саміт

1
"Я за свою думку", це справедливо, але ключове слово тут - "думка". Відповідь StackOverflow, можливо, не є оптимальним місцем для висловлення своєї думки. :)
— Скотт

1
Для запису я відредагував цю відповідь досить суттєво, оскільки її прийняли та проголосували. Але це все ще говорить однаково.
— Самміт Стіва

11

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

Спочатку давайте правильно відформатувати код:

#include<stdio.h>

int main(){
    int k = 0;
    while(+(+k--)!=0)
        k=k++;
    printf("%d\n", k);  
    return 0;
}

Тож тепер ми можемо бачити, що твердження k=k++;знаходиться всередині циклу.

Тепер давайте простежимо програму:

Коли умова циклу вперше оцінюється, kмає значення 0. Вираз k--має поточне значення k, яке дорівнює 0, і kзменшується як побічний ефект. Отже після цього твердження значення kдорівнює -1.

Ведучий +цього виразу не впливає на значення, тому його +k--оцінюють на 0 і аналогічно +(+k--)оцінюють на 0.

Потім !=оцінюється оператор. Оскільки 0!=0це помилково, тіло циклу не вводиться . Якби тіло було введено, ви б викликали невизначене поведінку, оскільки k=k++і читають, і записують kбез точки послідовності. Але цикл не вводиться, тому немає УБ.

Нарешті надрукується значення k, яке дорівнює -1.


1
Це відкрите, екзистенційне, філософське, неправдоподібне питання, чи не визначена невизначена поведінка, яка не виконується, не визначена. Хіба це не визначено? Я не думаю, що так.
— Стів Саміт

2
@SteveSummit Тут немає абсолютно нічого екзистенційного, філософського, неправдоподібного. if (x != NULL) *x = 42;Це не визначено, коли x == NULL? Звичайно, ні. Невизначена поведінка не відбувається в частинах коду, які не виконуються. Слово поведінка - натяк. Код, який не виконується, не має поведінки, не визначеної чи іншої.
— н. 'займенники' м.

1
@SteveSummit Якщо невизначена поведінка, яка не виконується, призведе до недійсності всієї програми, жодна програма C не визначила б поведінку. Тому що програми C - це завжди лише цикл / якщо умова далеко від виконання UB. Візьмемо для прикладу просту ітерацію масиву: вона триває, доки не пов'язана перевірка. Виконання наступної ітерації циклу було б невизначеною поведінкою, але оскільки це ніколи не виконується, все добре.
— cmaster - відновити

3
Я не збираюся сперечатися з цього приводу, тому що не займаюся мовним юристом. Але я думаю, що якщо ви поставили це запитання і позначили його мовою-юристом, у вас може виникнути бурхлива дискусія. Зауважимо, що k=k++якісно відрізняється від *x=42. Останній чітко визначений, якщо xє дійсним вказівником, але перший не визначений, незважаючи ні на що. (Зізнаюсь, ти можеш мати рацію, але знову ж таки, я не збираюся сперечатися з цим, і я все більше переживаю, що нас майстерно пропрацювали.)
— Стів Саміт

1
"Остання чітко визначена, якщо x є дійсним вказівником, але перший не визначений незалежно від того, що". Лише цілі програми можуть мати невизначене поведінку. Це поняття не застосовується до фрагментів коду, знятих ізольовано.
— н. 'займенники' м.

3

Ось версія цієї версії, яка показує перевагу оператора:

+(+(k--))

Два одинарні +оператори нічого не роблять, тому цей вираз точно рівносильний k--. Людина, яка написала це, швидше за все, намагалася зіпсуватись з вашим розумом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.