+ (+ 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.