Czytałem o naruszeniach kolejności oceny , a oni dają przykład, który mnie zastanawia.
1) Jeśli efekt uboczny na obiekcie skalarnym nie jest sekwencjonowany względem innego efektu ubocznego na tym samym obiekcie skalarnym, zachowanie jest niezdefiniowane.
// snip f(i = -1, i = -1); // undefined behavior
W tym kontekście i
jest obiektem skalarnym , co najwyraźniej oznacza
Typy arytmetyczne (3.9.1), typy wyliczeń, typy wskaźników, wskaźnik do typów elementów (3.9.2), std :: nullptr_t oraz wersje tych typów kwalifikowane przez CV (3.9.3) są wspólnie nazywane typami skalarnymi.
Nie rozumiem, w jaki sposób stwierdzenie jest w tym przypadku niejednoznaczne. Wydaje mi się, że niezależnie od tego, czy pierwszy lub drugi argument jest oceniany jako pierwszy, i
kończy się jako -1
i oba argumenty również -1
.
Czy ktoś może wyjaśnić?
AKTUALIZACJA
Naprawdę doceniam całą dyskusję. Jak dotąd bardzo podoba mi się odpowiedź @ harmic, ponieważ ujawnia pułapki i zawiłości w definiowaniu tego stwierdzenia, pomimo tego, jak prosto na pierwszy rzut oka wygląda. @ acheong87 wskazuje pewne problemy, które pojawiają się podczas korzystania z referencji, ale myślę, że jest to ortogonalne względem aspektu skutków ubocznych tego pytania.
PODSUMOWANIE
Ponieważ na to pytanie zwróciło się wiele uwagi, podsumuję główne punkty / odpowiedzi. Po pierwsze, pozwólcie mi na małą dygresję, aby wskazać, że „dlaczego” może mieć ściśle powiązane, ale subtelnie różne znaczenia, mianowicie „z jakiej przyczyny ”, „z jakiego powodu ” i „w jakim celu ”. Pogrupuję odpowiedzi, według których z tych znaczeń „dlaczego” się odnieśli.
z jakiej przyczyny
Główna odpowiedź tutaj pochodzi od Paula Drapera , a Martin J udzielił podobnej, ale nie tak obszernej odpowiedzi. Odpowiedź Paula Drapera sprowadza się do
Jest to niezdefiniowane zachowanie, ponieważ nie jest zdefiniowane, jakie jest zachowanie.
Odpowiedź jest ogólnie bardzo dobra, jeśli chodzi o wyjaśnienie tego, co mówi standard C ++. Zajmuje się również niektórymi powiązanymi przypadkami UB, takimi jak f(++i, ++i);
i f(i=1, i=-1);
. W pierwszym z pokrewnych przypadków nie jest jasne, czy pierwszym argumentem powinien być i+1
drugi i+2
i odwrotnie; po drugie, nie jest jasne, czy i
po wywołaniu funkcji powinna wynosić 1 czy -1. Oba te przypadki są UB, ponieważ podlegają następującej zasadzie:
Jeśli efekt uboczny na obiekcie skalarnym nie ma wpływu na inny efekt uboczny na tym samym obiekcie skalarnym, zachowanie jest niezdefiniowane.
Dlatego też f(i=-1, i=-1)
jest UB, ponieważ podlega tej samej zasadzie, mimo że intencja programisty jest (IMHO) oczywista i jednoznaczna.
Paul Draper również wyraźnie stwierdza w swoim wniosku, że
Czy mogło to być określone zachowanie? Tak. Czy to zostało zdefiniowane? Nie.
co prowadzi nas do pytania „z jakiego powodu / celu f(i=-1, i=-1)
pozostawiono zachowanie nieokreślone?”
z jakiego powodu / celu
Chociaż w standardzie C ++ występują pewne niedopatrzenia (być może nieostrożne), wiele pominięć jest dobrze uzasadnionych i służy konkretnemu celowi. Chociaż zdaję sobie sprawę z tego, że często celem jest albo „ułatwienie pracy kompilatora-pisarza”, albo „szybszy kod”, interesowało mnie przede wszystkim to, czy istnieje uzasadniony powód pozostawienia f(i=-1, i=-1)
UB.
harmic i supercat podają główne odpowiedzi, które stanowią przyczynę UB. Harmic wskazuje, że optymalizujący kompilator, który może rozbić pozornie atomowe operacje przypisywania na wiele instrukcji maszynowych i że może dalej przeplatać te instrukcje dla uzyskania optymalnej prędkości. Może to prowadzić do bardzo zaskakujących rezultatów: i
w jego scenariuszu kończy się na -2! W ten sposób harmonia pokazuje, jak przypisanie tej samej wartości zmiennej więcej niż raz może mieć złe skutki, jeśli operacje nie są konsekwentne.
supercat przedstawia pokrótce pułapki związane z próbami f(i=-1, i=-1)
zrobienia tego, na co wygląda. Wskazuje, że w niektórych architekturach istnieją twarde ograniczenia dotyczące wielokrotnego zapisu na ten sam adres pamięci. Kompilator może mieć trudności z uchwyceniem tego, jeśli mamy do czynienia z czymś mniej trywialnym niż f(i=-1, i=-1)
.
davidf dostarcza również przykład instrukcji przeplatania bardzo podobnych do instrukcji harmic.
Chociaż każdy z przykładów Harmika, Superkata i Davida jest nieco wymyślony, razem wzięte stanowią one konkretny powód, dla f(i=-1, i=-1)
którego zachowanie powinno być niezdefiniowane.
Zaakceptowałem odpowiedź Harmica, ponieważ najlepiej poradziła sobie ze wszystkimi znaczeniami tego, dlaczego, chociaż odpowiedź Paula Drapera lepiej odnosiła się do części „z jakiej przyczyny”.
inne odpowiedzi
JohnB zwraca uwagę, że jeśli weźmiemy pod uwagę przeciążone operatory przypisania (zamiast zwykłych skalarów), możemy również wpaść w kłopoty.
f(i-1, i = -1)
coś podobnego.
std::nullptr_t
oraz wersje tych typów zakwalifikowane do CV (3.9.3) są wspólnie nazywane typami skalarnymi . „