+ (+ k--) wyrażenie w C

9

Widziałem to pytanie w teście, w którym musimy podać dane wyjściowe następującego kodu.

#include<stdio.h>

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

Dane wyjściowe to -1. Nie jestem jednak pewien, dlaczego to jest odpowiedź.

Co to wyrażenie +(+k--)oznacza w C?

Ankur Gautam
źródło
4
Czy był tak sformatowany? To znaczy ;-)
Peter - Przywróć Monikę
3
Wskazówka: nigdy nie pisz takiego kodu BS w prawdziwym życiu.
Jabberwocky
5
To nie jest UB. Zobacz moją odpowiedź.
dbush
@ Peter-ReinstateMonica Tak, został sformatowany w ten sposób.
Ankur Gautam
2
Ze względu na swój wymyślony charakter i dziwaczne zwroty akcji - k=k++jest niezdefiniowany, ale nie jest nieokreślony, ponieważ nigdy nie jest wykonywany z powodu zaciemnionego stanu - głosuję za jego zamknięciem jako duplikat tego pytania .
Steve Summit

Odpowiedzi:

10

[Dla przypomnienia, edytowałem tę odpowiedź dość znacząco, odkąd została zaakceptowana i poddana pod głosowanie. Jednak nadal mówi w zasadzie te same rzeczy.]

Ten kod jest głęboko, być może celowo, zagmatwany. Zawiera wąsko unikniętą instancję przerażającego niezdefiniowanego zachowania . Zasadniczo nie można ustalić, czy osoba, która skonstruowała to pytanie, była bardzo, bardzo sprytna, czy bardzo, bardzo głupia. A „lekcja”, jaką ten kod może uczyć lub wypytywać o ciebie - a mianowicie, że operator jednoargumentowy plus niewiele robi - z pewnością nie jest wystarczająco ważna, aby zasłużyć na tego rodzaju wywrotowe złe kierowanie.

Istnieją dwa mylące aspekty kodu, dziwny warunek:

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

oraz oświadczenie, które kontroluje:

k=k++;

Najpierw omówię drugą część.

Jeśli masz taką zmienną k, którą chcesz zwiększyć o 1, C daje ci nie jeden, nie dwa, nie trzy, ale cztery różne sposoby:

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

Mimo tej nagrody (a może właśnie z tego powodu) niektórzy programiści są zdezorientowani i wykrztuszają skręty

k = k++;

Jeśli nie możesz zrozumieć, co to ma zrobić, nie martw się: nikt nie może. To wyrażenie zawiera dwie różne próby zmiany kwartości ( k =część i k++część), a ponieważ w C nie ma reguły określającej, która z prób modyfikacji „wygrywa”, wyrażenie takie jest formalnie niezdefiniowane , co oznacza nie tylko, że to nie zdefiniowane znaczenie, ale że cały program zawierający jest podejrzany.

Teraz, jeśli spojrzysz bardzo uważnie, zobaczysz, że w tym konkretnym programie linia k = k++nie jest tak naprawdę wykonywana, ponieważ (jak zobaczymy) warunek kontrolny jest początkowo fałszywy, więc pętla działa 0 razy . Więc ten konkretny program nie może faktycznie być zdefiniowana - ale to wciąż patologicznie mylące.

Zobacz także te kanoniczne SO odpowiedzi na wszystkie pytania dotyczące tego rodzaju niezdefiniowanego zachowania.

Ale nie pytałeś o tę k=k++część. Zapytałeś o pierwszą mylącą część, +(+k--)!=0warunek. To wygląda dziwnie, ponieważ jest dziwne. Nikt nigdy nie napisałby takiego kodu w prawdziwym programie. Nie ma więc powodu, aby nauczyć się go rozumieć. (Tak, to prawda, odkrywanie granic systemu może pomóc ci dowiedzieć się o jego drobnych punktach, ale w mojej książce jest dość wyraźna granica między pomysłowymi, prowokującymi do eksploracji eksploracjami a bzdurnymi, obraźliwymi eksploracjami, a to wyrażenie jest bardzo wyraźnie widoczne zła strona tej linii).

W każdym razie przyjrzyjmy się +(+k--)!=0. (A po zrobieniu tego, zapomnijmy o tym.) Każde takie wyrażenie musi być rozumiane od wewnątrz. Zakładam, że wiesz co

k--

robi. Pobiera kbieżącą wartość i „zwraca” ją do reszty wyrażenia i mniej więcej jednocześnie zmniejsza k, tzn. Zapisuje k-1ponownie wartość k.

Ale co to +robi? Jest to jednoskładnikowa, Plus, Plus nie binarny. To jest jak jednoargumentowy minus. Wiesz, że binarny minus robi odejmowanie: wyrażenie

a - b

odejmuje b od a. I wiesz, że jednoargumentowy minus neguje rzeczy: wyrażenie

-a

daje ci minus od. To, co +robi jedno , jest ... w zasadzie niczym. +adaje awartość po zmianie dodatnich wartości na dodatnie i ujemnych na ujemne. Więc wyrażenie

+k--

daje ci wszystko, co ci k--dało, czyli kstarą wartość.

Ale nie skończyliśmy, bo mamy

+(+k--)

To po prostu bierze wszystko, co +k--ci dano, i +znów odnosi się do tego jedno raz. Więc daje ci to, co ci +k--dało, co było k--ci, co dawało, co było kdawną wartością.

Tak więc w końcu warunek

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

robi dokładnie to samo, co znacznie bardziej zwyczajny warunek

while(k-- != 0)

zrobiłby. (Robi to samo, co while(+(+(+(+k--)))!=0)zrobiłby to nawet bardziej skomplikowany wygląd . I te nawiasy nie są tak naprawdę konieczne; robi to samo, co while(+ + + +k--!=0)by zrobił).

Nawet zastanawianie się, jaki jest „normalny” stan

while(k-- != 0)

jest dość trudne. W tej pętli dzieją się dwie rzeczy: Ponieważ pętla działa potencjalnie wiele razy, zamierzamy:

  1. k--rób dalej , aby robić kcoraz mniejsze, ale także
  2. dalej wykonuj ciało pętli, cokolwiek to zrobi.

Ale wykonujemy tę k--część od razu, zanim (lub w trakcie) decydujemy, czy podjąć kolejną podróż przez pętlę. I pamiętaj, że k--„zwraca” starą wartość kprzed jej zmniejszeniem. W tym programie początkowa wartość kwynosi 0. Więc k--„zwróci” starą wartość 0, a następnie zaktualizuje kdo -1. Ale reszta warunku jest != 0- ale jak właśnie widzieliśmy, przy pierwszym testowaniu warunku otrzymaliśmy 0. Więc nie będziemy robić żadnych pętli przez pętlę, więc nie będziemy próbować wykonać problematyczne stwierdzenie k=k++w ogóle.

Innymi słowy, w tej konkretnej pętli, chociaż powiedziałem, że „dzieje się coś w rodzaju dwóch rzeczy”, okazuje się, że rzecz 1 dzieje się raz, ale rzecz 2 dzieje się zero razy.

W każdym razie mam nadzieję, że teraz jest wystarczająco jasne, dlaczego ta marna wymówka dla programu kończy się na wydrukowaniu -1 jako wartości końcowej k. Zwykle nie lubię odpowiadać na pytania w quizie - czuję się jak oszukiwanie - ale w tym przypadku, ponieważ tak głośno nie zgadzam się z całym celem ćwiczenia, nie mam nic przeciwko.

Steve Summit
źródło
1
Przemyślane przykłady są typowe dla każdej podstawowej klasy. Jak inaczej napisać zwięzłe pytanie dotyczące pierwszeństwa operatora na egzaminie? Uczę matematyki, a gdybym miał nikiel za każdym razem, gdy uczeń pytał „Kiedy to zrobię w prawdziwym życiu?” ...
Scott
4
@Scott Jest wymyślony, a następnie wymyślony . Załóżmy, że jesteś instruktorem szkolenia kierowców. Załóżmy, że poprosisz swojego ucznia, aby przejechał przez duży ruch w centrum miasta, bijąc go kijem baseballowym o głowę. Prawdopodobnie uczeń nauczy się potencjalnie przydatnej umiejętności. Ale nazywam to nadużyciem. I podtrzymuję moją opinię, że kodeks w tym pytaniu jest obelżywy i ma negatywną wartość pedagogiczną.
Steve Summit
1
„Zgadzam się z opinią” to jest uczciwe, ale słowo kluczowe to „opinia”. Odpowiedź StackOverflow może nie jest optymalnym miejscem do wyrażania swoich opinii. :)
Scott
1
Dla przypomnienia, edytowałem tę odpowiedź dość znacząco, odkąd została zaakceptowana i zagłosowana. Jednak wciąż mówi w zasadzie te same rzeczy.
Steve Summit
11

Na pierwszy rzut oka wygląda na to, że ten kod wywołuje niezdefiniowane zachowanie, jednak tak nie jest.

Najpierw sformatujmy kod poprawnie:

#include<stdio.h>

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

Teraz widzimy, że instrukcja k=k++;znajduje się w pętli.

Teraz prześledźmy program:

Gdy warunek pętli jest oceniany po raz pierwszy, kma wartość 0. Wyrażenie k--ma bieżącą wartość k, która wynosi 0, i kjest zmniejszane jako efekt uboczny. Więc po tej instrukcji wartość kwynosi -1.

Początek +tego wyrażenia nie ma wpływu na wartość, dlatego jest +k--oceniany na 0 i podobnie +(+k--)ocenia się na 0.

Następnie !=operator jest oceniany. Ponieważ 0!=0jest to fałsz, treść pętli nie jest wprowadzana . Gdyby ciało zostało wprowadzone, wywołałbyś niezdefiniowane zachowanie, ponieważ k=k++zarówno czyta, jak i pisze kbez punktu sekwencyjnego. Ale pętla nie została wprowadzona, więc nie ma UB.

Na koniec kwypisywana jest wartość, która wynosi -1.

dbush
źródło
1
Jest to otwarte, egzystencjalne, filozoficzne, nie do rozważenia pytanie, czy niezdefiniowane zachowanie, które nie zostało wykonane, nie jest nieokreślone. Czy to nie jest niezdefiniowane? Nie wydaje mi się
Steve Summit
2
@SteveSummit Nie ma w tym absolutnie nic egzystencjalnego, filozoficznego, niepojętego. if (x != NULL) *x = 42;Czy to nie jest określone, kiedy x == NULL? Oczywiście nie. Niezdefiniowane zachowanie nie występuje w częściach kodu, które nie są wykonywane. Słowo zachowanie jest wskazówką. Kod, który nie jest wykonywany, nie ma zachowania, jest niezdefiniowany lub w inny sposób.
n. „zaimki” m.
1
@SteveSummit Jeśli niezdefiniowane zachowanie, które nie zostanie wykonane, unieważniłoby cały program, żaden program w C nie miałby zdefiniowanego zachowania. Ponieważ programy C są zawsze tylko pętlą / warunkiem warunkującym wykonanie UB. Weźmy na przykład prostą iterację tablicową: iteruje się do momentu niepowodzenia powiązanego sprawdzania. Wykonanie iteracji następnej pętli byłoby niezdefiniowanym zachowaniem, ale ponieważ nigdy się nie wykonuje, wszystko jest w porządku.
cmaster
3
Nie będę się o to kłócić, bo nie jestem prawnikiem językowym. Ale założę się, że jeśli zadasz to pytanie i oznaczysz je jako prawnik języka, możesz rozpocząć ożywioną debatę. Zauważ, że k=k++jakościowo różni się od *x=42. Ten drugi jest dobrze zdefiniowany, jeśli xjest poprawnym wskaźnikiem, ale ten pierwszy jest nieokreślony bez względu na wszystko. (Przyznaję, że masz rację, ale znowu nie zamierzam się z tym kłócić i jestem coraz bardziej zaniepokojony tym, że zostaliśmy po mistrzowsku trollowani).
Steve Summit
1
„Ten drugi jest dobrze zdefiniowany, jeśli x jest prawidłowym wskaźnikiem, ale ten pierwszy jest niezdefiniowany bez względu na wszystko”. Tylko całe programy mogą mieć niezdefiniowane zachowanie. To pojęcie nie ma zastosowania do fragmentów kodu branych osobno.
n. „zaimki” m.
3

Oto wersja tego, która pokazuje pierwszeństwo operatora:

+(+(k--))

Dwaj jednoargumentowi +nic nie robią, więc to wyrażenie jest dokładnie równoważne z k--. Osoba, która to napisała, najprawdopodobniej próbowała zepsuć ci umysł.

SS Anne
źródło