Почему эта версия логического И на C не показывает поведение короткого замыкания?
Да, это вопрос домашней работы, но я сделал свое исследование и много размышлений по этой теме и не могу понять это. В этом вопросе говорится, что этот кусок кода НЕ показывает поведение короткого замыкания и спрашивает, почему. Но мне кажется, что он проявляет короткое замыкание, поэтому кто-нибудь может объяснить, почему это не так?
В C:
int sc_and(int a, int b) {
return a ? b : 0;
}
Мне кажется, что в случае, когда a
является ложным, программа вообще не будет пытаться оценить b
, но я должен ошибаться. Почему программа даже прикоснется к b
в этом случае, когда ей не нужно?
Ответы
Ответ 1
Это трюк. b
- входной аргумент метода sc_and
, поэтому он всегда будет оцениваться. Другими словами sc_and(a(), b())
вызовет a()
и вызовет b()
(порядок не гарантируется), затем вызовите sc_and
с результатами a(), b()
, который переходит на a?b:0
. Это не имеет никакого отношения к самому тернарному оператору, который был бы абсолютно коротким.
UPDATE
Что касается того, почему я назвал этот "трюком": это из-за отсутствия четко определенного контекста для того, где следует рассматривать "короткое замыкание" (по крайней мере, как воспроизведено OP). Многие люди при задании только определения функции предполагают, что контекст вопроса задает вопрос о теле функции; они часто не рассматривают функцию как выражение само по себе. Это "трюк" вопроса; Напомню, что в программировании вообще, но особенно на таких языках, как C-like, которые часто имеют множество исключений из правил, вы не можете этого сделать. Например, если вопрос был задан как таковой:
Рассмотрим следующий код. Будет ли sc_и показывать поведение короткого замыкания при вызове из main:
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;
}
Было бы сразу ясно, что вы должны думать о sc_and
как о собственном само по себе в своем собственном языке, специфичном для домена, и оценивать, является ли вызов sc_and
проявляет короткое замыкание, как обычный &&
. Я бы не подумал, что это вообще трюк, потому что ясно, что вы не должны фокусироваться на тройном операторе и вместо этого должны сосредоточиться на механике функций-вызовов C/С++ (и, я думаю, вписывающийся в следующий вопрос, чтобы написать sc_and
, который выполняет короткое замыкание, в котором будет использоваться #define
, а не функция).
Независимо от того, называете ли вы, что тернарный оператор выполняет короткое замыкание (или что-то еще, например "условная оценка" ), зависит от вашего определения короткого замыкания, и вы можете прочитать различные комментарии для размышлений об этом. По-моему, это так, но это не очень важно для реального вопроса или почему я назвал его "трюком".
Ответ 2
Когда утверждение
bool x = a && b++; // a and b are of int type
выполняется, b++
не будет оцениваться, если операнд a
оценивается как false
(поведение короткого замыкания). Это означает, что побочный эффект на b
не будет иметь места.
Теперь посмотрим на функцию:
bool and_fun(int a, int b)
{
return a && b;
}
и назовите это
bool x = and_fun(a, b++);
В этом случае, если a
равно true
или false
, b++
всегда будет оцениваться во время вызова функции, а побочный эффект на b
всегда будет иметь место.
То же самое верно для
int x = a ? b : 0; // Short circuit behavior
и
int sc_and (int a, int b) // No short circuit behavior.
{
return a ? b : 0;
}
Ответ 3
Как уже указывалось другими, независимо от того, что передается в функцию в качестве двух аргументов, она получает оценку по мере ее передачи. Это путь до операции tenary.
С другой стороны, это
#define sc_and(a, b) \
((a) ?(b) :0)
будет "короткое замыкание", так как этот макрос не подразумевает вызов функции, и при этом не выполняется оценка аргумента (ов) функции.
Ответ 4
Отредактировано для исправления ошибок, отмеченных в комментарии @cmasters.
В
int sc_and(int a, int b) {
return a ? b : 0;
}
... выражение return
ed показывает оценку короткого замыкания, но вызов функции не выполняется.
Попробуйте позвонить
sc_and (0, 1 / 0);
Вызов функции оценивает 1 / 0
, хотя он никогда не используется, поэтому вызывает, вероятно, деление на нулевую ошибку.
Соответствующие выдержки из (черновика) стандарта ANSI C:
2.1.2.3 Выполнение программы
...
В абстрактной машине все выражения оцениваются в соответствии с семантика. Фактическая реализация не должна оценивать часть выражение, если оно может вывести, что его значение не используется и что нет требуются побочные эффекты (в том числе вызванные вызовом функция или доступ к энергозависимому объекту).
и
3.3.2.2 Функциональные вызовы
....
Семантика
...
При подготовке к вызову функции аргументы вычисляются, и каждому параметру присваивается значение соответствующего аргумент.
Я предполагаю, что каждый аргумент оценивается как выражение, но список аргументов как целого не является выражением, поэтому поведение, отличное от SCE, является обязательным.
Как брызговик на поверхности глубоких вод стандарта С, я был бы признателен за надлежащее информированное представление по двум аспектам:
- Оценивает ли
1 / 0
поведение undefined?
- Является ли список аргументов выражением? (Я думаю, не)
P.S.
Даже вы переходите на С++ и определяете sc_and
как функцию inline
, вы не получите SCE. Если вы определяете его как макрос C, как это делает @alk, вы обязательно это сделаете.
Ответ 5
Чтобы четко увидеть тройное короткое замыкание, попробуйте немного изменить код, чтобы использовать указатели функций вместо целых чисел:
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;
}
И затем скомпилируйте его (даже при оптимизации почти без изменений: -O0
!). Вы увидите, что b()
не выполняется, если a()
возвращает false.
% gcc -O0 tershort.c
% ./a.out
I'm a() returning 0
%
Здесь сгенерированная сборка выглядит так:
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:
Так как другие правильно указали, что вопрос заключается в том, чтобы сосредоточить внимание на тройном операторском поведении, которое имеет короткое замыкание (назовем это "условной оценкой" ) вместо оценки параметров при вызове (вызов по значению), который НЕ является коротким схема.
Ответ 6
Терминальный оператор C никогда не может быть короткозамкнутым, поскольку он оценивает только одно выражение a (условие), чтобы определить значение, заданное выражениями b и c, если любое значение может быть возвращено.
Следующий код:
int ret = a ? b : c; // Here, b and c are expressions that return a value.
Это почти эквивалентно следующему коду:
int ret;
if(a) {ret = b} else {ret = c}
Выражение a может быть сформировано другими операторами, такими как && или || что может быть короткое замыкание, потому что они могут оценивать два выражения перед возвратом значения, но это не будет считаться тройным оператором, выполняющим короткое замыкание, но операторы, используемые в условии, как это происходит в регулярном if.
Update:
Существует несколько дискуссий о том, что тройной оператор является оператором короткого замыкания. Аргумент говорит, что любой оператор, который не оценивает все его операнды, делает короткое замыкание в соответствии с @aruisdante в комментарии ниже. Если дано это определение, то тернарный оператор будет закорочен, и в этом случае это первоначальное определение я согласен. Проблема в том, что термин "короткое замыкание" изначально использовался для определенного типа оператора, который допускал это поведение, и это логические/логические операторы, и причина, по которой это только то, что я попытаюсь объяснить.
Следуя статье "Обнаружение коротких замыканий" , оценка короткого замыкания относится только к логическим операторам, внедренным в язык, таким образом, что знание что первый операнд сделает второй неуместным, то есть для && оператор является первым операндом false, а для || оператор является первым операндом true, спецификация C11 также отмечает это в 6.5.13 Логический оператор AND и 6.5.14 Логический оператор ИЛИ.
Это означает, что для определения короткого замыкания вы должны определить его в операторе, который должен оценивать все операнды точно так же, как и логические операторы, если первый операнд не делает ненужным второй. Это соответствует тому, что написано в другом определении короткого замыкания в MathWorks в разделе "Логическое короткое замыкание", поскольку короткое замыкание происходит от логических операторов.
Как я пытался объяснить C-тернарный оператор, также называемый тройным, если он оценивает только два из операндов, он оценивает первый, а затем оценивает второй, либо один из двух оставшихся в зависимости от значение первого. Он всегда делает это, и не должен оценивать все три в любой ситуации, поэтому в любом случае нет "короткого замыкания".
Как всегда, если вы видите, что что-то не так, напишите комментарий с аргументом против этого, а не только с downvote, что просто делает SO хуже, и я считаю, что мы можем быть гораздо лучшим сообществом, просто ответы с нисходящими ответами не согласны.