Dlaczego ta wersja logicznego AND w C nie wykazuje zachowania w postaci zwarcia?

80

Tak, to jest zadanie domowe, ale przeprowadziłem swoje badania i sporo przemyślałem na ten temat i nie mogę tego rozgryźć. Pytanie stwierdza, że ​​ten fragment kodu NIE wykazuje zachowania zwarciowego i pyta dlaczego. Ale wydaje mi się, że wykazuje zachowanie zwarciowe, więc czy ktoś może wyjaśnić, dlaczego tak nie jest?

W C:

Wydaje mi się, że w przypadku afałszu program w ogóle nie będzie próbował oceniać b, ale chyba się mylę. Dlaczego bw tym przypadku program w ogóle się styka , skoro nie musi?

Bobazonski
źródło
11
Jak to jest typowe dla większości wymyślonych pytań do zadań domowych, nigdy nie zobaczysz tego rodzaju kodu w systemie produkcyjnym, chyba że programista celowo stara się być sprytny.
Robert Harvey
49
@RobertHarvey Cały czas będziesz widzieć dokładnie taki kod w systemach produkcyjnych! Nie jest to prawdopodobnie funkcja o nazwie AND(), ale funkcje odbierające argumenty według wartości, a następnie oceniające je (lub nie) w zależności od logiki funkcji są wszędzie. Pomimo, że jest to „podchwytliwe pytanie”, zrozumienie jego zachowania jest krytycznym zachowaniem. W języku z wezwaniem po imieniu na to pytanie można by odpowiedzieć zupełnie inaczej.
Ben Jackson
7
@BenJackson: Komentowałem kod, a nie zachowanie. Tak, musisz zrozumieć zachowanie. Nie, nie musisz pisać takiego kodu.
Robert Harvey
3
Jest to całkiem istotne, jeśli kiedykolwiek będziesz musiał kodować w VB i napotkać IIf. Ponieważ jest to funkcja, a nie operator, ocena nie jest zwarta. Może to powodować problemy dla programistów przyzwyczajonych do operatora zwarcia, który następnie pisze takie rzeczy IIf(x Is Nothing, Default, x.SomeValue).
Dan Bryant
3
@alk bo to oskarżeniem systemu edukacji.
Shivan Dragon

Odpowiedzi:

118

To podchwytliwe pytanie. bjest argumentem wejściowym sc_andmetody, więc zawsze będzie obliczana. Innymi słowy sc_and(a(), b())zadzwoni a()i zadzwoni b()(zamówienie nie jest gwarantowane), a następnie zadzwoni sc_andz wynikami, a(), b()które przejdą do a?b:0. Nie ma to nic wspólnego z samym operatorem trójskładnikowym, który spowodowałby absolutne zwarcie.

AKTUALIZACJA

Jeśli chodzi o powód, dla którego nazwałem to „podchwytliwym pytaniem”: to z powodu braku dobrze zdefiniowanego kontekstu, w którym należy rozważyć „zwarcie” (przynajmniej tak, jak zostało to odtworzone w PO). Wiele osób, gdy podaje się tylko definicję funkcji, zakłada, że ​​kontekstem pytania jest pytanie o treść funkcji ; często nie traktują tej funkcji jako wyrażenia samej w sobie. Na tym polega „sztuczka” pytania; Aby przypomnieć, że w programowaniu w ogóle, ale szczególnie w językach takich jak C-like, które często mają wiele wyjątków od reguł, nie możesz tego zrobić. Przykład, jeśli pytanie zostało zadane jako takie:

Rozważmy następujący kod. Sc_and wykaże zachowanie zwarciowe, gdy zostanie wywołane z main :

Byłoby to od razu jasne, że jesteś ma być myślenie sc_andjako operator w sobie w swoim własnym języku specyficzne dla domeny , i oceny, czy wezwanie do sc_andeksponatów zachowanie zwarcie jak regularna &&będzie . Nie uważałbym tego za podchwytliwe pytanie, ponieważ jasne jest, że nie powinieneś skupiać się na operatorze trójskładnikowym, a zamiast tego powinieneś skupić się na mechanice wywołań funkcji C / C ++ (i, jak sądzę, prowadzić ładnie do pytania uzupełniającego, aby napisać, sc_andże powoduje zwarcie, co wymagałoby użycia #defineraczej niż funkcji).

To, czy nazwiesz to, co sam operator trójskładnikowy wywołuje zwarciem (lub czymś innym, jak „ocena warunkowa”), zależy od twojej definicji zwarcia i możesz przeczytać różne komentarze, aby przemyśleć to. Według mnie tak jest, ale nie jest to szczególnie istotne dla rzeczywistego pytania lub dlaczego nazwałem to „sztuczką”.

aruisdante
źródło
13
Operator trójskładnikowy kręci się krótko? Nie. Oblicza jedno z wyrażeń następujących po ?znaku, tak jak w if (condition) {when true} else {when-false}. To się nie nazywa zwarciem.
Jens
17
@Jens: Każdy przypadek, w którym operator pomija ocenę jednego lub więcej swoich operandów, nazwałbym formą „zwarcia”, ale tak naprawdę jest tylko kwestia tego, jak zdecydujesz się zdefiniować termin.
R .. GitHub STOP HELPING ICE
13
@Jens To cukier syntaktyczny dla an if else, a nie if. A nawet wtedy tak naprawdę nie jest; ?:operator jest ekspresja , if-elseto oświadczenie . Są to bardzo różne semantycznie rzeczy, nawet jeśli można skonstruować równoważny zestaw instrukcji, które dają taki sam efekt jak wyrażenie trójskładnikowe . Dlatego uważa się, że powoduje zwarcie; większość innych wyrażeń zawsze oblicza wszystkie ich operandy ( +,-,*,/itp.). Kiedy tego nie robią, jest to zwarcie ( &&, ||).
aruisdante
9
Tak, dla mnie ważną różnicą jest to, że mówimy o operatorach. Oczywiście instrukcje kontroli przepływu kontrolują, która ścieżka kodu jest wykonywana. Dla operatorów nie jest oczywiste dla kogoś, kto nie jest zaznajomiony z językami pochodnymi C i C, że operator może nie oceniać wszystkich swoich operandów, więc ważne jest, aby mieć termin na określenie tej właściwości, a do tego używam „short circuiting ”.
R .. GitHub STOP HELPING ICE
43

Kiedy oświadczenie

executes, b++nie będzie oceniana, jeśli operand został aoszacowany na false(zachowanie w przypadku zwarcia). Oznacza to, że efekt uboczny bnie wystąpi.

Teraz spójrz na funkcję:

i nazwij to

W tym przypadku to, czy ajest, trueczy false, b++będzie zawsze oceniane 1 podczas wywołania funkcji i bzawsze będzie miał miejsce efekt uboczny .

To samo dotyczy

i


1 Kolejność obliczania argumentów funkcji nie jest określona.

haccks
źródło
1
OK, więc zgadzając się z odpowiedzią Stephena Quana: czy jest możliwe (legalne), że kompilacja mogłaby wbudować funkcję „and_fun”, tak że gdy wywołujesz „bool x = and_fun (a, b ++);” b ++ nie zostanie zwiększone, jeśli a jest prawdziwe?
Shivan Dragon
4
@ShivanDragon To brzmi dla mnie jak zmiana obserwowalnego zachowania.
sapi
3
@ShivanDragon: Podczas wstawiania kompilator nie zmieni zachowania. To nie jest tak proste, jak zastąpienie ciała funkcji w.
Jack Aidley,
Dla jasności możesz dodać int x = a ? b++ : 0, jako obserwowalne zwarcie.
Paul Draper,
@PaulDraper; Zachowałem oryginalny fragment, ponieważ jest on dla czytelników, aby nie mylić.
haccks
19

Jak już zauważyli inni, bez względu na to, co zostanie przekazane do funkcji jako dwa argumenty, jest ona oceniana w miarę przekazywania. To jest na długo przed operacją dzierżawną.

Z drugiej strony to

spowodowałoby "zwarcie", ponieważ to makro nie implikuje wywołania funkcji, a przy tym nie jest wykonywana ocena argumentów funkcji.

alk
źródło
może wyjaśnij dlaczego? Wygląda dla mnie dokładnie tak samo, jak fragment z op. Z wyjątkiem tego, że jego wbudowany element ma inny zakres.
dhein
5

Edytowano, aby poprawić błędy odnotowane w komentarzu @cmasters.


W

... returnwyrażenie ed wykazuje ocenę zwarcia, ale wywołanie funkcji nie.

Spróbuj zadzwonić

Wywołanie funkcji oblicza 1 / 0, chociaż nigdy nie jest używane, powodując - prawdopodobnie - błąd dzielenia przez zero.

Odpowiednie fragmenty (projektu) normy ANSI C to:

2.1.2.3 Wykonanie programu

...

W maszynie abstrakcyjnej wszystkie wyrażenia są oceniane zgodnie z semantyką. Rzeczywista implementacja nie musi oceniać części wyrażenia, jeśli może wywnioskować, że jego wartość nie jest używana i że nie są wytwarzane żadne potrzebne efekty uboczne (w tym wszelkie spowodowane wywołaniem funkcji lub dostępem do obiektu ulotnego).

i

3.3.2.2 Wywołania funkcji

....

Semantyka

...

Przygotowując się do wywołania funkcji, argumenty są oceniane, a każdemu parametrowi przypisywana jest wartość odpowiadającego mu argumentu.

Domyślam się, że każdy argument jest oceniany jako wyrażenie, ale lista argumentów jako całość nie jest wyrażeniem, stąd zachowanie inne niż SCE jest obowiązkowe.

Jako rozpryskujący się na powierzchni głębokich wód standardu C doceniłbym odpowiednio poinformowany pogląd na dwa aspekty:

  • Czy ocenianie 1 / 0powoduje niezdefiniowane zachowanie?
  • Czy lista argumentów jest wyrażeniem? (Myślę, że nie)

PS

Nawet jeśli przejdziesz do C ++ i zdefiniujesz sc_andjako inlinefunkcję, nie otrzymasz SCE. Jeśli zdefiniujesz to jako makro C, tak jak robi to @alk , z pewnością to zrobisz.

Miniaturka
źródło
1
Nie, inlinefunkcja nie zmienia semantyki wywołania funkcji. I te semantyki określają, że wszystkie argumenty oceniane, jak poprawnie zacytowałeś. Nawet jeśli kompilator może zoptymalizować połączenie, widoczne zachowanie nie musi się zmienić, to znaczy sc_and(f(), g())musi zachowywać się tak, jakby obie f()i g()są zawsze dzwonił. sc_and(0, 1/0)to zły przykład, ponieważ jest to niezdefiniowane zachowanie, a kompilator nie musi nawet wywoływać sc_and()...
cmaster - reinstate monica
@cmaster Dziękuję. Po prostu nie wiedziałem, że C ++ inlinezachowała semantykę wywołań. Utknąłem przy zerowym podziale, ponieważ pasuje do tego przykładu, i myślę, że SCE jest często wykorzystywane do uniknięcia niezdefiniowanego zachowania.
Miniatura
4

Aby wyraźnie zobaczyć trójskładnikowe zwarcie op, spróbuj nieco zmienić kod, aby używać wskaźników funkcji zamiast liczb całkowitych:

A potem skompiluj go (nawet prawie BEZ optymalizacji:! -O0). Zobaczysz, że b()nie jest wykonywany, jeśli a()zwróci false.

Tutaj wygenerowany montaż wygląda następująco:

Tak więc, jak słusznie wskazali inni, sztuczka polegająca na pytaniu polega na tym, abyś skupił się na trójskładnikowym zachowaniu operatora, który powoduje zwarcie (nazwij to „oceną warunkową”), zamiast oceny parametrów podczas wywołania (wezwanie według wartości), która NIE powoduje zwarcia.

flaviodesousa
źródło
0

Operator C trójskładnikowy nigdy nie zwarcie, ponieważ tylko jeden ocenia ekspresji (warunkiem), w celu określenia wartości podanej przez wyrażenia b i c , o ile może być zwrócony dowolna.

Poniższy kod:

Jest to prawie równoważne z następującym kodem:

Wyrażenie a może być utworzone przez inne operatory, takie jak && lub || które mogą spowodować zwarcie, ponieważ mogą oszacować dwa wyrażenia przed zwróceniem wartości, ale nie byłoby to traktowane jako operator trójskładnikowy powodujący zwarcie, ale operatory używane w warunku, tak jak w zwykłej instrukcji if.

Aktualizacja:

Trwa dyskusja, czy operator trójskładnikowy jest operatorem zwarcia. Argument mówi, że każdy operator, który nie ocenia wszystkich swoich operandów, wykonuje zwarcie zgodnie z @aruisdante w komentarzu poniżej. Gdyby podano tę definicję, operator trójskładnikowy byłby zwarty iw przypadku, gdy jest to oryginalna definicja, zgadzam się. Problem polega na tym, że termin „zwarcie” był pierwotnie używany w odniesieniu do określonego rodzaju operatora, który pozwalał na takie zachowanie i są to operatory logiczne / boolowskie, a powód, dla którego są tylko takie, postaram się wyjaśnić.

Zgodnie z artykułem Ocena zwarcia, ocena zwarcia odnosi się tylko do operatorów logicznych zaimplementowanych w języku w taki sposób, że wiedząc, że pierwszy argument sprawi, że drugi będzie nieistotny, to znaczy dla operatora && będącego pierwszym operandem fałszywym , a dla || operator jest pierwszym operandem, który jest prawdziwy , specyfikacja C11 zauważa to również w 6.5.13 Operator logiczny AND i 6.5.14 Operator logiczny OR.

Oznacza to, że aby zidentyfikować zachowanie zwarciowe, można by oczekiwać, że zostanie ono zidentyfikowane w operatorze, który musi ocenić wszystkie operandy, tak jak operatory boolowskie, jeśli pierwszy operand nie sprawia, że ​​drugi jest nieistotny. Jest to zgodne z tym, co jest napisane w innej definicji zwarcia w MathWorks w sekcji „Zwarcie logiczne”, ponieważ zwarcie pochodzi od operatorów logicznych.

Ponieważ próbowałem wyjaśnić operator trójskładnikowy, nazywany również trójskładnikowym, jeśli oblicza tylko dwa argumenty, oblicza pierwszy, a następnie ocenia drugi, jeden z dwóch pozostałych w zależności od wartości pierwszy. Robi to zawsze, nie powinno oceniać wszystkich trzech w żadnej sytuacji, więc w żadnym wypadku nie ma „zwarcia”.

Jak zawsze, jeśli zauważysz, że coś jest nie tak, napisz komentarz z argumentem przeciwko temu, a nie tylko negatywnym głosem, to tylko pogarsza wrażenia z SO i wierzę, że możemy być znacznie lepszą społecznością, która po prostu przegrywa z którym się nie zgadza.

Adrián Pérez
źródło
3
A po co głos przeciw? Chciałbym skomentować, żebym mógł naprawić wszystko, co powiedziałem źle. Prosimy o konstruktywne podejście.
Adrián Pérez
2
Może -1, ponieważ nie jest to w 100% na temat. ale w każdym razie dałem +1, ponieważ twoja odpowiedź przynajmniej wyjaśnia to statemant i nie wydaje się być błędna ompv.
dhein
4
@AdrianPerez zobacz sekcję komentarzy w mojej odpowiedzi, aby dowiedzieć się, dlaczego większość ludzi w 100% uważa, że ​​operator terniary to obwód zwarty. Jest to wyrażenie, które nie oblicza wszystkich swoich operandów na podstawie wartości niektórych operandów. To zwarcie.
aruisdante
3
Nie przychodzi mi do głowy żaden inny operator logiczny, który nie ocenia wszystkich swoich operandów. Ale to nie ma znaczenia; Powód, dla którego nazywa się to zwarciem, jest precyzyjny, ponieważ jest to operator (lub w ogólnym sensie raczej wyrażenie niż instrukcja), który nie ocenia wszystkich swoich operandów. Rodzaj operatora tak naprawdę nie ma znaczenia. Byłoby całkowicie możliwe napisanie operatorów boolowskich, które nie powodują zwarcia, i można by również napisać operatory inne niż boolowskie, które też to zrobiły (np. Mnożenie liczb całkowitych z 0 zawsze wynosi 0, więc natychmiast zwróć 0, jeśli pierwszy operand ma wartość 0).
aruisdante
2
x = a ? b : 0;jest semantycznie taka sama jak(a && (x = b)) || (x = 0);
jxh