1. C++ / Говнокод #19385

    +9

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    17. 17
    18. 18
    19. 19
    20. 20
    21. 21
    22. 22
    23. 23
    int naive_show_int(int x) {
        char buf[32];
        char *p = buf + sizeof(buf);
        *--p = 0;
        int negative = 0;
        if (x < 0) {
            x = -x;
            negative = 1;
        }
        while (x > 0) {
            if (x <= 0)
                return -1;
            int digit = '0' + x % 10;
            if (digit < '0' || digit >= '9')
                return -1;
            *--p = digit;
            x /= 10;
        }
        if (negative)
            *--p = '-';
        puts(p);
        return 0;
    }

    Допустишь один UB - ничто уже не спасёт твою прогу...

    http://ideone.com/EFGoBi

    Запостил: bormand, 02 Февраля 2016

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

    • Компилятор уверен в корректности кода на 146%, никакие assert'ы и if'ы не помогают его разубедить...
      Ответить
      • какие такие assert'ы и if'ы, в невыполнившемся while шоле?
        Ответить
        • Я уж после http://govnokod.ru/19385#comment312049 почти поверил, что он действительно в while не входит и печатает мусор, но потом на всякий случай поставил старую добрую печать: http://ideone.com/F9npb8
          Т.е. он в while входит с отрицательным x, затем игнорирует все проверки внутри цикла. Творится сущая хренота.
          Ответить
          • Да там всё просто. Компилятор предполагает, что после первого if'а x >= 0 (случай с 0x80000000 - UB, на него можно забить). И цикл делает через jne (т.е. x != 0). Ну и все проверки убирает, т.к. при x > 0 они не сработают.

            P.S. Кстати, деление на 10 он заменил умножением на 1717986919.
            Ответить
            • > Он формально доказал, что digit >= '0' && digit <= '9'
              > Ну и все проверки убирает
              А в случае сужения диапазона что творится? Это поддаётся объяснению?
              http://ideone.com/DHWxQN
              Ответить
              • Всё просто. Проверка с восьмёркой не выкомпиливается, но всё равно прокатывает в рантайме т.к. весь этот мусор явно меньше '8'. Проверка с единицей тоже не выкомпиливается, но не прокатывает в рантайме, т.к. мусор меньше '1'.

                P.S. А вот про семёрку непонятно, надо дизасм глядеть.
                Ответить
              • Разобрался...

                Короче, в случае с восьмёркой компилятор видит только одно кривое число '9'. С ним он и сравнивает, а потом делает je. Т.е. получается if (digit == '9') return -1.

                В случае с семёркой он честно сравнивает с семёркой, но юзает ja, как для unsigned'ов (ведь digit не может быть отрицательным). Т.е. if ((unsigned)digit > 7u) return -1. Но digit у нас отрицательный, т.е. 0xFFFFFчётотам, что явно больше 7. Поэтому и происходит return -1.

                Два косяка случайно скомпенсировали друг друга и получилось правильное поведение. UB такой UB.
                Ответить
                • Намерения компилятора тут уже теряются (по крайней мере, для меня).

                  > Но digit у нас отрицательный, т.е. 0xFFFFFчётотам
                  Однако, новость. Компилятор считает, что т.к. x>0, можно просто от x % 10 откусить один байт и сложить с '0'? А нет, там int же. Компилятор считает, что т.к. x>0, можно просто от x % 10 откусить один байт и сложить с '0' потому, что потом значение используется как char?
                  Ответить
                  • Нет, компилятор рассуждает так. x > 0, значит и x % 10 > 0. Т.е. можно беззнаково сравнить x % 10 с 7 и выйти, если x % 10 больше 7. А если всё ок - добавить '0', записать в массив и продолжить цикл. Как-то так асм выглядит.
                    Ответить
        • Ты на ideone то глянь, фома неверующий. Цикл 10 раз выполнился с отрицательным числом. И обе проверки внутри цикла не сработали.

          Там сначала были assert'ы, потом на if'ы поменял, чтобы не докапывались "да у тебя же ассёрты выкокомпилились".
          Ответить
        • Невыполнившемся? Лолшто: http://ideone.com/dHgPxN
          Ответить
      • Кстати, где-то я видел статью про "сишку в 2016", ЕМНИП, там был этот пример. Опять же, ЕМНИП, советовали использовать unsigned для беззнаковой части (т.е. после определения знака).
        Ответить
        • > после определения знака
          Хе-хе. А UB то это не убирает (переполнение знакового числа в операторе -), просто заметает под ковёр до поры до времени... Пока в компилятор не добавят новую хитровыебанную оптимизацию, которая понадеется, что старший бит этого unsigned'а всегда будет нулём...

          Или там предлагают число кастануть в unsigned, а минус заменить на битовую магию?
          Ответить
        • > хитровыебанную оптимизацию, которая понадеется, что старший бит этого unsigned'а всегда будет нулём
          Ололо, она таки есть! http://ideone.com/oFheOh

          Т.е. (unsigned)(-x) тоже неплохо стреляет в колено...

          Т.е. остаётся только сначала кастовать в unsigned (благо, емнип, это стандарту не противоречит и вполне документировано) и в нём уже делать отрицание.
          Ответить
          • Test: 80000000 & 80000000 == ​00000000
            Shifts: 00000080 00000040 00000020 00000010 00000008 00000004 00000002 0000000​0
            Ответить
      • http://melpon.org/wandbox/permlink/sxhPQXOCEDtM42pS clang с опцией -fsanitize=undefined тут ошибку четко ловит в рантайме

        prog.c:9:17: runtime error: negation of -2147483648 cannot be represented in type 'int'; cast to an unsigned type to negate this value to itself

        Да и в новых GCC этот -fsanitize=undefined вроде как появился
        Ответить
    • Отличнейший пример, спасибо. Пишите ещё
      Ответить
      • Причём компилятор провёл неплохой анализ... Он формально доказал, что digit >= '0' && digit <= '9' для всех не UB'овых ситуаций, раз смог выбросить проверку...
        Ответить
    • > if (digit < '0' || digit >= '9')
      > return -1;

      А за что так с девяткой?
      Ответить
      • Опечатка, видимо, была. Кстати, на ideone bormand исправил.

        Но взгляните сюда: http://ideone.com/DHWxQN (разные ограничения на digit)
        Какой эффект!
        Ответить
      • Просто опечатка. На ideone исправлено.
        Ответить
    • А можете ткнуть где здесь UB?
      Ответить
      • x = -x при x == 0x80000000

        Цитата из стандарта нужна?
        Ответить
        • Сорри, я не перешел по ссылке на ideone. Подумал что код в принципе не работает.

          0x80000000 разве int? Тогда уж -0x80000000
          Ответить
      • Ту:
        > int x;
        > x = -x;
        Спойлер: http://www.cplusplus.com/reference/climits/
        Ответить
    • Ugly Bullshit
      Ответить
    • Кстати.
      Где здесь C++, bormand?!
      Ответить
    • int shit(void)
      {
          int x = 0;
          while ( (x != -x) || (x == 0) )
          {
              x++;
          }
          return x;
      }

      http://goo.gl/0K4VFR - c оптимизацией в GCC получаем бесконечный цикл. Без оптимизации (или с оптимизацией -O1) возвращает -2147483648
      Ответить
      • Обсуждали же уже и внезапно бесконечные циклы, и одновременно истинные и не истинные булы.
        Вот ещё пример конечного/бесконечного цикла в зависимости от оптимизаций. Пикантности прибавляет факт, что условие цикла не зависит от UB. Но, наличие UB не гарантирует работу даже частей программы до него.

        #include <iostream>
        
        int main()
        {
            for (int i = 0; i < 4; ++i)
                std::cout << i*1000000000 << std::endl;
        }
        Ответить
    • > while (x > 0) {
      > x /= 10;

      Простите мне мою пупость. Вы што, ожидали получить отрицателное число при делении 2 положительных???
      или я что-то проглядел?
      Ответить
      • https://ideone.com/62vysK
        ???????????????????

        ЗЫ. переполнение?
        Ответить
        • Да.
          -1  1
          -2  2
          ...
          -2147483647  2147483647
          -2147483648  ??????????
          Ответить
        • > while (x)
          Хех, не научились они пока доказывать инварианты по индукции. Вот и не выбросило проверки. С while (x > 0) инвариант цикла более явный, там выбрасывает "невыполнимые" ветки.
          Ответить
      • Я снова сморозилл...
        Ответить

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