Почему эта версия логического И на 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 хуже, и я считаю, что мы можем быть гораздо лучшим сообществом, просто ответы с нисходящими ответами не согласны.