Niedawno w wywiadzie pojawiło się następujące pytanie typu obiektywnego.
int a = 0;
cout << a++ << a;
Odpowiedzi:
za. 10
b. 01
c. niezdefiniowane zachowanie
Odpowiedziałem na wybór b, tj. Wyjście będzie „01”.
Ale ku memu zdziwieniu później ankieter powiedział mi, że prawidłowa odpowiedź to opcja c: nieokreślona.
Teraz znam koncepcję punktów sekwencji w C ++. Zachowanie jest niezdefiniowane dla następującej instrukcji:
int i = 0;
i += i++ + i++;
ale zgodnie z moim zrozumieniem dla tego stwierdzenia cout << a++ << a
, ostream.operator<<()
zostanie wywołane dwukrotnie, najpierw z, ostream.operator<<(a++)
a później ostream.operator<<(a)
.
Wynik sprawdziłem też na kompilatorze VS2010 i na wyjściu jest też '01'.
10
, byłoby albo01
albo00
. (c++
będzie zawsze szacowana do wartości, którac
miała przed zwiększeniem). I nawet gdyby nie było nieokreślone, nadal byłoby strasznie zagmatwane.Odpowiedzi:
Możesz myśleć o:
Tak jak:
C ++ gwarantuje, że wszystkie efekty uboczne poprzednich ocen zostaną wykonane w punktach sekwencji . Nie ma punktów sekwencji pomiędzy oceną argumentów funkcji, co oznacza, że argument ten
a
może być oceniany przed argumentemstd::operator<<(std::cout, a++)
lub po nim. Wynik powyższego jest więc nieokreślony.Aktualizacja C ++ 17
W C ++ 17 zasady zostały zaktualizowane. W szczególności:
Co oznacza, że wymaga kodu do wygenerowania wyniku
b
, który generuje01
.Więcej informacji można znaleźć w dokumencie P0145R3 Refining Expression Evaluation Order for Idiomatic C ++ .
źródło
c
ma typint
,operator<<
oto funkcje składowe .operator<<
to, czy jest funkcją składową, czy wolnostojącą, nie wpływa na punkty sekwencji.So the result of the above is undefined.
Twoje wyjaśnienie jest dobre tylko dla nieokreślonych , a nie dla nieokreślonych . James Kanze wyjaśnił jednak, że jego odpowiedź jest tym bardziej nieokreślona .Technicznie rzecz biorąc, ogólnie jest to niezdefiniowane zachowanie .
Ale są dwa ważne aspekty odpowiedzi.
Instrukcja kodu:
jest oceniany jako:
Norma nie definiuje kolejności obliczania argumentów funkcji.
Więc albo:
std::operator<<(std::cout, a++)
jest oceniany jako pierwszy luba
jest oceniany jako pierwszy lubTo zamówienie jest nieokreślone [Ref 1] zgodnie z normą.
[Odn. 1] C ++ 03 5.2.2 Wywołanie funkcji,
pkt 8
Co więcej, nie ma punktu sekwencji między oceną argumentów funkcji, ale punkt sekwencji istnieje tylko po ocenie wszystkich argumentów [Ref 2] .
[Odn. 2] C ++ 03 1.9 Wykonanie programu [wprowadzenie.wykonanie]: Par.
17:
Zauważ, że tutaj wartość
c
jest dostępna więcej niż jeden raz bez interweniującego punktu sekwencji, w związku z tym standard mówi:[Odn. 3] C ++ 03 5 Wyrażenia [wyraż]:
par. 4:
Kod modyfikuje
c
więcej niż jeden raz bez interwencji punktu sekwencji i nie jest uzyskiwany dostęp do niego w celu określenia wartości przechowywanego obiektu. Jest to wyraźne naruszenie powyższej klauzuli, a zatem wynik zgodnie z zaleceniami normy to Undefined Behavior [Ref 3] .źródło
Punkty sekwencji definiują tylko częściowe uporządkowanie. W twoim przypadku masz (po rozwiązaniu problemu):
Istnieje punkt sekwencji między
a++
pierwszym wywołaniemstd::ostream::operator<<
a i punktem sekwencji między drugima
a drugim wywołaniemstd::ostream::operator<<
, ale nie ma punktu sekwencji międzya++
aa
; jedynymi ograniczeniami porządkowania są te, którea++
są w pełni oceniane (w tym skutki uboczne) przed pierwszym wywołaniemoperator<<
, a drugie wa
pełni oceniane przed drugim wywołaniemoperator<<
. (Istnieją również przyczynowe ograniczenia porządku: drugie wywołanieoperator<<
nie może poprzedzać pierwszego, ponieważ wymaga jako argumentu wyników pierwszego). § 5/4 (C ++ 03) stwierdza:Jedną z dopuszczalnych porządków twojej wypowiedzi jest
a++
,a
po pierwsze wywołanieoperator<<
, drugie wywołanieoperator<<
; to modyfikuje przechowywaną wartośća
(a++
) i uzyskuje do niej dostęp w inny sposób niż w celu określenia nowej wartości (drugieja
), zachowanie jest niezdefiniowane.źródło
c
typ użytkownika ze zdefiniowanym przez użytkownika++
, zamiast tegoint
, wyniki byłyby nieokreślone, ale nie byłoby niezdefiniowanego zachowania.c
wfoo(foo(bar(c)), c)
? Istnieje punkt sekwencji, gdy wywoływane są funkcje i kiedy zwracają, ale nie jest wymagane wywołanie funkcji między wartościami tych dwóchc
.c
był to UDT, przeciążone operatory byłyby wywołaniami funkcji i wprowadzałyby punkt sekwencji, więc zachowanie nie byłoby niezdefiniowane. Ale nadal byłoby nieokreślone, czy wyrażenie podrzędnec
zostało ocenione przed, czy poc++
, więc to, czy otrzymałeś wersję zwiększoną, czy nie, nie byłoby określone (i teoretycznie nie musiałoby być takie samo za każdym razem).c
ic++
, zatem dwa mogą występować w dowolnej kolejności. Jeśli chodzi o średniki ... Powodują punkt sekwencji tylko wtedy, gdy są pełnymi wyrażeniami. Innymi ważnymi punktami sekwencji są wywołanie funkcji:f(c++)
zobaczy zwiększonyc
inf
i operator przecinka&&
,||
a?:
także spowoduje punkty sekwencji.Prawidłowa odpowiedź to zadać pytanie. To stwierdzenie jest nie do przyjęcia, ponieważ czytelnik nie widzi jasnej odpowiedzi. Innym sposobem spojrzenia na to jest to, że wprowadziliśmy efekty uboczne (c ++), które znacznie utrudniają interpretację instrukcji. Zwięzły kod jest świetny, pod warunkiem, że ma jasne znaczenie.
źródło