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:
int sc_and(int a, int b) {
return a ? b : 0;
}
Wydaje mi się, że w przypadku a
fałszu program w ogóle nie będzie próbował oceniać b
, ale chyba się mylę. Dlaczego b
w tym przypadku program w ogóle się styka , skoro nie musi?
c
short-circuiting
Bobazonski
źródło
źródło
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.IIf(x Is Nothing, Default, x.SomeValue)
.Odpowiedzi:
To podchwytliwe pytanie.
b
jest argumentem wejściowymsc_and
metody, więc zawsze będzie obliczana. Innymi słowysc_and(a(), b())
zadzwonia()
i zadzwonib()
(zamówienie nie jest gwarantowane), a następnie zadzwonisc_and
z wynikami,a(), b()
które przejdą doa?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:
int sc_and(int a, int b){ return a?b:0; } int a(){ cout<<"called a!"<<endl; return 0; } int b(){ cout<<"called b!"<<endl; return 1; } int main(char* argc, char** argv){ int x = sc_and(a(), b()); return 0; }
Byłoby to od razu jasne, że jesteś ma być myślenie
sc_and
jako operator w sobie w swoim własnym języku specyficzne dla domeny , i oceny, czy wezwanie dosc_and
eksponató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#define
raczej 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ą”.
źródło
?
znaku, tak jak wif (condition) {when true} else {when-false}
. To się nie nazywa zwarciem.x Sand y
(przy użyciu Sand do oznaczenia odmiany zwarciowej) jest równoważne wyrażeniu warunkowemu,if x then y else false;
któremux Sor y
jest równoważneif x then true else y
. .if else
, a nieif
. A nawet wtedy tak naprawdę nie jest;?:
operator jest ekspresja ,if-else
to 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 (&&, ||
).Kiedy oświadczenie
bool x = a && b++; // a and b are of int type
executes,
b++
nie będzie oceniana, jeśli operand została
oszacowany nafalse
(zachowanie w przypadku zwarcia). Oznacza to, że efekt ubocznyb
nie wystąpi.Teraz spójrz na funkcję:
bool and_fun(int a, int b) { return a && b; }
i nazwij to
bool x = and_fun(a, b++);
W tym przypadku to, czy
a
jest,true
czyfalse
,b++
będzie zawsze oceniane 1 podczas wywołania funkcji ib
zawsze będzie miał miejsce efekt uboczny .To samo dotyczy
int x = a ? b : 0; // Short circuit behavior
i
int sc_and (int a, int b) // No short circuit behavior. { return a ? b : 0; }
1 Kolejność obliczania argumentów funkcji nie jest określona.
źródło
int x = a ? b++ : 0
, jako obserwowalne zwarcie.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
#define sc_and(a, b) \ ((a) ?(b) :0)
spowodowałoby "zwarcie", ponieważ to makro nie implikuje wywołania funkcji, a przy tym nie jest wykonywana ocena argumentów funkcji.
źródło
Edytowano, aby poprawić błędy odnotowane w komentarzu @cmasters.
W
int sc_and(int a, int b) { return a ? b : 0; }
...
return
wyrażenie ed wykazuje ocenę zwarcia, ale wywołanie funkcji nie.Spróbuj zadzwonić
sc_and (0, 1 / 0);
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:
i
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:
1 / 0
powoduje niezdefiniowane zachowanie?PS
Nawet jeśli przejdziesz do C ++ i zdefiniujesz
sc_and
jakoinline
funkcję, nie otrzymasz SCE. Jeśli zdefiniujesz to jako makro C, tak jak robi to @alk , z pewnością to zrobisz.źródło
inline
funkcja nie zmienia semantyki wywołania funkcji. I te semantyki określają, że wszystkie argumenty są oceniane, jak poprawnie zacytowałeś. Nawet jeśli kompilator może zoptymalizować połączenie, widoczne zachowanie nie musi się zmienić, to znaczysc_and(f(), g())
musi zachowywać się tak, jakby obief()
ig()
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()
...inline
zachował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.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:
int a() { printf("I'm a() returning 0\n"); return 0; } int b() { printf("And I'm b() returning 1 (not that it matters)\n"); return 1; } int sc_and(int (*a)(), int (*b)()) { a() ? b() : 0; } int main() { sc_and(a, b); return 0; }
A potem skompiluj go (nawet prawie BEZ optymalizacji:!
-O0
). Zobaczysz, żeb()
nie jest wykonywany, jeślia()
zwróci false.% gcc -O0 tershort.c % ./a.out I'm a() returning 0 %
Tutaj wygenerowany montaż wygląda następująco:
call *%rdx <-- call a() testl %eax, %eax <-- test result je .L8 <-- skip if 0 (false) movq -16(%rbp), %rdx movl $0, %eax call *%rdx <- calls b() only if not skipped .L8:
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.
źródło
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:
int ret = a ? b : c; // Here, b and c are expressions that return a value.
Jest to prawie równoważne z następującym kodem:
int ret; if(a) {ret = b} else {ret = c}
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.
źródło
x = a ? b : 0;
jest semantycznie taka sama jak(a && (x = b)) || (x = 0);