1. Куча / Говнокод #23535

    +2

    1. 1
    https://shmat-razum.blogspot.com/2012/12/blog-post.html

    Есть такое правило, к которому все привыкли: если в программе записано логическое выражение с and, и первое подвыражение оказалось равно false, то второе не вычисляется. Аналогично с or: если первое подвыражение равно true, второе не вычисляется. Это позволяет удобно записывать вещи вида

    if (index < 0 || array[index] == NULL)
    ...

    или
    if (index >= 0 && array[index] == ptr)
    ...


    Это правило действует во всех широко используемых языках программирования: C/C++, Java, C#, Javascript, Python, а также в многочисленных представителях семейств лиспов и смолтоков. И даже в Хаскеле. И даже в PL/SQL. У правила есть название: закорачивание логических связок (short-circuit evaluation). Казалось, оно само собой разумеется, и есть во всех языках.

    При столкновении с языком Fortran автора ждал сильный удар. Закорачивание в этом языке не просто не действует, а может действовать или не действовать в зависимости от воли компилятора. Эта неопределённость закреплена в стандарте. При этом, в интеловском компиляторе, например, просто нет ключа, чтобы этим управлять. Программист не может быть уверен, что выполнится лишь одна ветвь; в то же время, он не может быть уверен, что выполнятся обе. Поведение может зависеть от номера версии компилятора и настроек оптимизации.

    Запостил: j123123, 15 Ноября 2017

    Комментарии (29) RSS

    • Фортран достаточно старый ЯП, я думаю что он имеет право так делать.
      Кстати, во многих скриптовых (perl, php) есть и НЕ short-circuit вариант. Выглядит он внешне как побитовое: & или |.
      Ответить
    • Вот от такого UB (unspecified behavior) намного больший багор, чем от каких-то там UB с присвоениями в сишечке
      Ответить
      • какой багор ))

        Вот почему бывает полезно почитать про язык, прежде чем на нём писать.
        В другой раз вот так сядешь за неизвестный язык, и окажется что оператор "&&" в нем удаляет все файлы с диска, и шлет матерное письмо маме программиста
        Ответить
        • См. мой говнокод про мускуль, где я ёбнул все записи в таблице, т.к. палки оказались or а не конкатенацией.
          Ответить
      • > от такого UB (unspecified behavior) намного больший багор, чем от каких-то там UB с присвоениями в сишечке

        Да нормально всё. Раньше все языки такими были. То есть вообще везде и всюду and и or были жадными. И в алголе, и в паскале, и бейсике, и в фортране, и в аде.

        Только анскилябры заедушные не могут осилить нормальный порядок вычислений. Дейкстра бы не одобрил подобной early-exit хуиты.


        even C programming can be appreciated by the Real Programmer: after all, there's no type checking, variable names are seven (ten? eight?) characters long, and the added bonus of the Pointer data type is thrown in. It's like having the best parts of FORTRAN and assembly language in one place. (Not to mention some of the more creative uses for #define.)

        As long as there are ill-defined goals, bizarre bugs, and unrealistic schedules, there will be Real Programmers willing to jump in and Solve The Problem, saving the documentation for later. Long live FORTRAN!
        Ответить
    • В Турбо-Паскале и в Дельфи можно выбирать, какая схема используется: после директивы {$B-} все вычисления идут по короткой схеме, а после {$B+} все вычисления идут по полной схеме.

      А в языке Ада для короткой и полной схемы существуют разные ключевые слова: просто or и and для полной схемы, а or else и and then для сокращённой. Второе слово в этом случае подсказывает, когда будет вычисляться второй аргумент (слово «else» подсказывает, что первый должен быть false; «then» подсказывает, что первый должен быть true).
      Ответить
    • >При столкновении с языком Fortran автора ждал сильный удар. Закорачивание в этом языке не просто не действует, а может действовать или не действовать в зависимости от воли компилятора. Эта неопределённость закреплена в стандарте.

      Как однажды меня подколоол bugmenot: "вот и выросло поколение, которое не знает Паскаля".
      Кстати в том самом трушном бейсике оно никогда и не работало.
      Ответить
    • И НА САМОМ ДЕЛЕ и в фортране, и в бейсике и в поцкале всё было.

      А именно мега-фича однострочных ifчиков (без then!). Которую юзали повсеместно. Применив правило Де-Моргана можно разрулить вообще все мыслимые случаи.
      IF a() IF NOT b() IF c() PRINT *,"some conditianal shit!"


      >не просто не действует, а может действовать или не действовать в зависимости от воли компилятора
      Это не баг, а фича.
      Т.к. всегда была возможность оптимизации (как и {$B-}), навроде -O3 в gcc.

      Только у ограниченных крестопитухов и их унылых последователей выполнение &, | нельзя оптимизировать по короткой схеме, а у божественных фортранщиков можно.
      Ответить
      • Давайте разберёмся в синтаксисе каждого языка.

        В Фортране 66 и в более старых были такие конструкции:
        IF (<условие>) <действие>
        IF (<условие>) GO TO <метка>

        Это «иф логический». THEN в Фортране не используется, но нужны скобки у условия. Слово ELSE ещё не придумали, так что у него всего одна ветвь.
        IF (<выражение>) <действие1>, <действие2>, <действие3>

        Это «иф арифметический». Действие1 выполняется, если выражение < 0, действие2 — если выражение = 0, действие3 — если выражение > 0. Это почти аналог тернарника, только у него три ветви.

        В Фортране 77 появился блочный IF со словом THEN (до Фортрана 77 были только спагетти из GO TO). В блочном ифе наконец-то появились ELSE и ELSE IF.

        В Фортране 90 «арифметический» иф задепрекейтили, но не удалили.

        *****

        В Бейсике были строчные ифы:
        IF <условие> THEN <действие> [ ELSE <действие> ]
        IF <условие> THEN <метка> [ ELSE <метка> ]

        Два варианта ифа отличались по формату того, что стоит после слова THEN: если стояло число, оно воспринималось как метка.

        В микрософтовском Кубейсике появился блочный IF, стыренный из Фортрана 77.

        *****

        Наконец, в Паскале изначально был свободный формат с единственным вариантом ифа:
        IF <условие> THEN <действие> [ ELSE <действие> ]
        Ответить
      • Сводим всё вместе.
        Фортран 66:
        IF (a()) IF (.NOT. b()) IF (c()) PRINT *,"some conditianal shit!"

        Да, у Фортрана были «фирменные» логические операторы .NOT., .EQ., .NE., .LT., .GT., .LE., .GE., .AND., .OR., по всей видимости внесённые любителями Ассемблера.

        Бейсик:
        IF a() THEN IF NOT b() THEN IF c() PRINT "some conditianal shit!"


        Паскаль:
        IF a THEN IF NOT b THEN IF c THEN WriteLn('some conditianal shit!');


        Теперь вроде всё точно.
        Ответить
        • Да, всё именно так. Хотя насчёт THENа в бейсике... Вроде я когда-то видел такой диалект в котором он необязателен, как и в фортране.


          Добавлю что если в фортране N действий писалось через запятую, то в бейсике для этого было двоеточие.
          Фортран:
          >IF (<выражение>) <действие1>, <действие2>, <действие3>

          Бейсик:
          IF (<выражение>) THEN <действие1>: <действие2>: <действие3>
          Ответить
          • И тут ошибка. В Бейсике ВСЕ действия, разделённые двоеточиями, выполнялись, если условие истинно. В Фортране же запятые использовались только в так называемом «арифметическом ифе» с особой логикой.

            Бейсик:
            IF x > 3 THEN PRINT "В лесу" : PRINT "родилась" : PRINT "ёлочка."

            Если x > 3, напечатает:
            В лесу
            родилась
            ёлочка.
            Иначе не напечатает ничего.

            Фортран-66:
            IF (x - 3) 100, 200, 300
            100 PRINT *, "FOO"
            GO TO 400
            200 PRINT *, "BAR"
            GO TO 400
            300 PRINT *, "BAZ"
            400
            Напечатает только одну строку: "FOO", если x - 3 < 0; "BAR", если x - 3 = 0; "BAZ", если x - 3 > 0.
            Ответить
          • Полез гуглить. Несколько действий в одной строке стало можно писать в Фортране-90, когда появился свободный формат. Разделялись действия точкой с запятой, как в Си и в Паскале.

            Почему нельзя использовать запятую? Запятая в Фортране используется для разделения аргументов PRINT и прочих стандартных вызовов и в древних конструкциях вроде DO, IF. Поэтому, кстати, в «арифметическом ифе» писали три метки, а не три выражения (если я напишу три принта, то парсер сломается при попытке определить, для чего используется запятая).
            Ответить
        • > Вроде я когда-то видел такой диалект в котором он необязателен, как и в фортране.

          Не, это я с фортраном путаю. Там GOTO было необязательное. Обязательный THEN нужен парсеру чтоб скобки унылые не ставить.

          IF A=B THEN GOTO 50
          а можно так:
          IF A=B THEN 50
          Ответить
          • А в Spectrum Basic AND и OR ленивы по первому аргументы:
            x AND y
            - возвращает x, если y<>0
            - возвращает 0, если y=0 (или "", если x - строка)

            Хотя нет, не уверен насчет лени (что будет, если написать SQR(-1) AND 0 ?), но можно использовать для всяких конструкций:
            PRINT STR$(N) + " green bottle" + ("s" AND N<>1)
            Ответить
    • Странно вообще. Вы ж лоу-левелщик, а удивляетесь как анскильная питушня. Поясню.

      & эквивалентен AND
      | эквивалентен OR
      ^ эквивалентен XOR

      Это означает что они работают как и их математические аналоги. То есть для однобитных интов (булевского типа) как обычные логические функции. ОНИ ЖЕ используются для целых как побитовые операции. Так же как и в сиподобных.

      Здесь как и у математических аналогов должны выполняться свойства: комутативности, ассоциативности и дистрибутивности. То есть результат должен быть инвариантом от порядка вычислений.

      Возьмём более низкоуровневый пример, учитывая что фортран обычно использовался для вычислений.

      Как известно бранчинг - довольно дорогая операция. Компилятор может догадаться что выполнить 1-2 лишних инструкции, гораздо менее затратно ошибиться с веткой. К тому же процессоры могут иметь несколько исполнительных блоков, и если инструкции не зависят друг от друга, то они будут выполняться assпаралельно.

      && - это не AND, но фактически сахарок над вложенными IFами. Которые затем превращаются в условные переходы.

      (a>x) AND (diff<0.0000001) AND (i<=n)

      Потом, компилятор может менять местами условия, вынося наперёд более простые для расчёта.
      Например он знает что на данной платформе медленный плавающий питух, потому он может перенести сравнение флоатов в конец.
      Если кодер желает явно задать порядок вычислений язык даёт ему возможность (см. примеры выше).

      Вроде как очевидные вещи.
      Ответить
      • Можно пойти дальше, например если нужно 4 бита наложить друг на друга со сдвигом, т.е
        uint8_t shit (uint8_t a, uint8_t b)
        {
          return (a << 4) | b
        }

        то на каком-нибудь особо ебанутом процессоре может оказаться так, что инструкция сложения будет работать быстрее инструкции побитового OR. И настоящий царский язык должен позволять например сделать так
        uint8_t shit (uint8_t a, uint8_t b)
        {
          uint8_t result;
          do_something_from_this()
          {
            option 1:
            result = ((a << 4) | b)
            break;
            option 2:
            result = ((a << 4) + b)
            break;
          }
          return result;
        }

        и компилятор сам на основе эвристик чтоб выбрал, какой код из предложенных вариантов ему компилировать
        Ответить
        • Или может научить компилятор доказывать, что если во всех местах в коде, где вызывается данная функция, переменная b только 4 бита полезной информации в себе содержит, а остальные биты всегда занулены, то тогда побитовый or можно заменять сложением
          Ответить
          • i am a very baaaad boy
            Ответить
          • > переменная b только 4 бита полезной информации в себе содержит
            Гцц вполне умеет работать с такими простыми утверждениями о переменных. Где-то у меня был говнокод, где он настолько был уверен в диапазоне переменной, что выбрасывал к хуям проверки, ассёрты, сдвиги, арифметику и т.п.

            Проблема в том, что нету средств чтобы ему эту инфу передать явно.
            Ответить
    • Программист не может быть уверен, что вычисления будут идти в заданном порядке; в то же время, он не может быть уверен, что компилятор или процессор изменят порядок инструкций.

      Поведение может зависеть от номера версии компилятора и настроек оптимизации.

      КАК СТРАШНО ЖИТЬ!
      Ответить
    • При столкновении с языком "C++" автора ждал сильный удар. Закорачивание в этом языке может действовать или не действовать в зависимости от того, нативный оператор или перегруженный. Эта неопределённость закреплена в стандарте.

      bool func1();
      bool func2();
      bool z = func1() || func2(); // здесь закорачивание действует.
      // func2() не будет вызываться, если func1() вернёт true.
      
      class Pituh {
      public:
        const Pituh operator||(const Pituh& rv) const {
          return something;
        }
      }
      
      Pituh a, b, c;
      c = a || b; // А здесь закорачивание не действует, потому что || перегружен.
      Ответить
    • Помню как я перешел с Pascal на c.

      Фигурная скобка означает начало комментария.
      Это правило действует во всех широко используемых языках программирования: Pascal.
      Казалось, оно само собой разумеется, во всех языках.

      При столкновении с языком Си автора ждал сильный удар....
      Ответить
      • Гораздо интереснее, когда сишники начинают писать на Паскале. У них тела всех функций оказываются пустыми, потому что «фигурная скобка означает начало комментария».
        Ответить
      • Помню как я перешел с Pascal на С++.

        Там = означает начало сравнение.
        Это правило действует во всех широко используемых языках программирования: Pascal, Basic, Algol, Cobol.
        Казалось, оно само собой разумеется, во всех языках.

        При столкновении с языком Си++ автора ждал сильный удар. Сравнение в этом языке не просто не действует, а может действовать или не действовать в перегрузки операторов. Эта неопределённость закреплена в стандарте.
        Ответить

    Добавить комментарий