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


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.


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.


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ł.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.