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

    0

    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
    24. 24
    template <typename T, typename OUT_T = uint8_t> 
    OUT_T subdecoder_nbt::extract_bits(T bits, uint8_t pos, uint8_t end)
    {
        auto invert_bytes = [](T bytes) -> T
        {
            auto *p     = reinterpret_cast<uint8_t*>(&bytes), 
                 *p_end = reinterpret_cast<uint8_t*>(&bytes) + sizeof(bytes) - 1; 
            for(; p < p_end; ++p, --p_end)
            {
                *p = *p ^ *p_end;
                *p_end = *p ^ *p_end;
                *p = *p ^ *p_end;
            }
    
            return bytes;
        };
    
        bits = invert_bytes(bits);
    
        bits <<= pos;
        bits >>= (sizeof(bits) * 8 - (end - pos) - 1);
    
        return (OUT_T)bits;
    }

    Как правильно доставать биты из промежутка из стандартных типов C++ на x86.
    Изучал эту проблему в сумме почти сутки.

    А всё потому, что x86 хранит байты в Little-Endian, из-за чего при сдвиге биты окажутся не там, где ожидаешь.

    Запостил: YpaHeLI_, 23 Октября 2020

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

    • Сам говнокод выглядит так же, только без invert_bytes
      Ответить
    • Little-endian влияет только на порядок байтов, если что.
      Ответить
      • Ну т.е. с битами и сдвигами всё ок. У тебя само число неправильно прочитано, арифметика на нём тоже работать не будет.

        З.Ы. invert_bytes аля htons/htonl/bswap_64 обычно делают сразу после чтения, до любой работы с числом, а не посреди неё.
        Ответить
        • Классический сишный способ:
          // читаем структуру из файла
          Header hdr;
          // ... тут вызов read ...
          
          // и фиксим endian во всех полях с числами длиннее байта
          hdr.field16 = htons(hdr.field16);
          hdr.field32 = htonl(hdr.field32);
          
          // теперь с ними можно спокойно работать
          // биты и арифметика ведут себя как положено
          Ответить
          • А так можно читать структуру? Структуры на всех платформах имеют одинаковый сдвиг? Недавно обсуждалось, что bool внутри может хранится как угодно.

            P.S. ты отредактировал комментарий, там был read
            Ответить
            • Обычно в таких структурах только кошерные типы и все поля аккуратно разложены.
              Ответить
              • Мы на работе сериализуем в protobuf. Это позволяет безболезненно добавлять и удалять поля. Нахуй ещё задумываться о такой хуйне типа внутреннего строения и индейцах.
                Ответить
                • Протобуф уже научился юзать чужеродные структуры не в его формате?
                  Ответить
                  • Хз. Ты хочешь написать структуру в "C", и чтобы он сгенерил описание в своём формате?
                    Ответить
                    • Я не хочу её писать, но многие структуры уже написаны за годы до нас. И десериализатор протобуфа на них, увы, не натянуть.
                      Ответить
                      • CastXML может выдать XML файл с описанием структур. Для
                        #include <inttypes.h>
                        #include <stddef.h>
                        
                        struct uint8pair
                        {
                          uint8_t a;
                          uint8_t b;
                        };
                        
                        struct uint16pair
                        {
                          uint16_t a;
                          uint16_t b;
                        };


                        будет какое-то такое описание:
                        ...
                          <Typedef id="_12" name="__uint8_t" type="_111" context="_1" location="f1:37" file="f1" line="37"/>
                          <Typedef id="_14" name="__uint16_t" type="_112" context="_1" location="f1:39" file="f1" line="39"/>
                        ...
                          <Typedef id="_66" name="uint8_t" type="_12" context="_1" location="f3:24" file="f3" line="24"/>
                          <Typedef id="_67" name="uint16_t" type="_14" context="_1" location="f3:25" file="f3" line="25"/>
                        ...
                          <FundamentalType id="_111" name="unsigned char" size="8" align="8"/>
                          <FundamentalType id="_112" name="short unsigned int" size="16" align="16"/>
                        ...
                          <Struct id="_104" name="uint8pair" context="_1" location="f8:4" file="f8" line="4" members="_132 _133" size="16" align="8"/>
                          <Struct id="_105" name="uint16pair" context="_1" location="f8:10" file="f8" line="10" members="_134 _135" size="32" align="16"/>
                        ...
                          <Field id="_132" name="a" type="_66" context="_104" access="public" location="f8:6" file="f8" line="6" offset="0"/>
                          <Field id="_133" name="b" type="_66" context="_104" access="public" location="f8:7" file="f8" line="7" offset="8"/>
                          <Field id="_134" name="a" type="_67" context="_105" access="public" location="f8:12" file="f8" line="12" offset="0"/>
                          <Field id="_135" name="b" type="_67" context="_105" access="public" location="f8:13" file="f8" line="13" offset="16"/>
                        ...

                        Можно потом из этого XML генерить протобуфную питушню какую-нибудь
                        Ответить
                        • > протобуфную питушню

                          Дык у протобуфа парсер в принципе не пригоден для кастомных форматов. Ибо там что-то в духе тегированных полей. Или что-то изменилось?

                          Ну что-то своё можно сгенерить. На крестах в общем-то просто имён полей хватает для генерации, остальное в компайлтайме доступно.
                          Ответить
            • > там был read

              read() я убрал потому что я обосрался с проверкой его результата (signed-unsigned mismatch).
              Ответить
        • Классический способ с побайтовой десериализацией:
          // ... убедились, что в буфере есть sizeof(T) байт ...
          
          // и теперь читаем их из буфера как big-endian
          T res = 0;
          for (size_t i = 0; i < sizeof(T); ++i)
              res = (res << 8) | (uint8_t)buf[i];
          
          // теперь с res можно спокойно работать
          // биты и арифметика ведут себя как положено
          Ответить
        • У меня приходит header, который нельзя менять.
          Ответить
          • Ну тогда, имхо, надо всё равно разделить переворачивание числа и выдирание битов.
            Ответить
      • Да, но если есть стык, то например, если тебе нужен отрезок 7-12,
        то окажется, что не инвертировав байты ты достанешь не оттуда.
        там вообще в этот отрезок войдет еще один отрезок, который ему не должен принадлежать.

        15 14 13 [12] 11 10 9 8 | [7] 6 5 4 3 2 1 0 | LE
        ~~~~~~~~ -------------------------- ~~~~~~~~~~~
        [7] 6 5 4 3 2 1 0 | 15 14 13 [12] 11 10 9 8 | BE
        --- ~~~~~~~~~~~~~~~~~~~~ --------------------
        Ответить
    • Блин, нафиг ты пилишь xor-swap в строках 10-12, олимпиадник штоле? Есть же стандартный std::swap.
      Ответить
      • Я бы в одну строку залепил.
        Ответить
        • Я бы тоже:
          a ^= b ^= a ^= b;

          Сейас прилетит питух, и скажет, что это UB
          Ответить
          • > и скажет, что это UB

            Или не скажет, а потом у тебя это в продакшене навернётся.
            Ответить
      • Минуснулслучайно, куриной лапой тяжело мышью рулить.
        Ответить
      • Потому что это стильно, модно, молодежно!
        На самом деле я хз как устроен std::swap для стандартных типов.
        Используется ли там xor-обмен.
        Ответить
        • > используется ли там xor-обмен

          xor-обмен никто в здравом уме не юзает, разве что выебнуться. Впрочем, компиляторы его умеют оптимизировать в один xchg. Как и классическую перестановку через временную переменную, которая юзается в дефолтном std::swap.
          Ответить
          • Кстати XCHG между регистром и памятью это относительно дорогая операция т.к. шина блокируется.
            Ответить
            • показать все, что скрытоvanished
              Ответить
              • https://www.agner.org/optimize/instruction_tables.pdf
                > Instructions with a LOCK prefix have a long latency that depends on cache organization and possibly RAM speed. If there are multiple processors or cores or direct memory access (DMA) devices, then all locked instructions will lock a cache line for exclusive access, which may involve RAM ac-cess. A LOCK prefix typically costs more than a hundred clock cycles, even on single-processor systems. This also applies to the XCHG instruction with a memory operand.
                Ответить
            • > шина блокируется

              Зачем? Зачем? Даже при обычном xchg, а не lock xchg у которого гарантии?

              З.Ы. Бля, и правда. If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL.
              Ответить
              • У инженеров интеля спроси. Наверное было много скверно написанных программ, где под xchg подразумевали lock xchg. И их сделали одним и тем же.
                Ответить
                • То есть раньше было одно ядро, и великой разницы между lock xchg и xchg не было, и тупые лалки понаписали без лока, а потом интел завез SMP, и побоялся сломать старые программы?
                  Ответить
                  • Какой багор )))
                    Ответить
                  • Если они так боялись сломать старый софт, то почему не добавили автоматический lock и ко всяким add?

                    И какой-нибудь флажок smp-aware в eflags, который отключает все автоблокировки.
                    Ответить
                  • > интел завёз SMP

                    Лол, в доке по оригинальному 8086 уже есть глава про Multiprocessing Features! И там xchg ещё не брал lock автоматически.

                    The 8086 and 8088 are designed for the multiprocessing environment. They have built-in features that help solve the coordination problems that have discouraged multiprocessing system development in the past.

                    LOCK may be used in multiprocessing systems to coordinate access to a common resource.

                    It is possible for another processor to obtain the bus between these two cycles and to gain access to the partially-updated semaphore. This can be prevented by preceding the XCHG instruction with a LOCK prefix.
                    Ответить
                    • А вот в 286 уже так написано: When used with a memory operand, XCHG automatically activates the LOCK signal.

                      Видимо какие-то лалки уже успели обосраться за 4 года.
                      Ответить
                      • показать все, что скрытоvanished
                        Ответить
                        • А я кажется понял почему...

                          LOCK на двойке был привилегированной инструкцией! А мутексов в юзермоде то хотелось. Вот и прикрутили автолочку к XCHG.

                          80386 перестал проверять привилегии на LOCK. Но уже было поздно.

                          Такой вот очередной плевок в вечность.
                          Ответить
                          • показать все, что скрытоvanished
                            Ответить
                            • Х.з., возможно тогда LOCK ещё на любых инструкциях работал, включая всякую строковую фигню? Я не вижу в доке от двойки ограничения на конкретный набор инструкций, которое в свежих доках есть.
                              Ответить
                            • Короче да, в доке по 80386 есть глава Restricted Semantics of LOCK. И замечание, что при неправильном использовании LOCK теперь может вылететь invalid opcode.
                              Ответить
                              • показать все, что скрытоvanished
                                Ответить
                                • Походу да.

                                  The 80386 defines new exceptions that can occur even in systems designed for the 80286.

                                  Exception #6 - invalid opcode

                                  This exception can result from improper use of the LOCK instruction.
                                  Ответить
                                • The 80286 processor implements the bus lock function differently than the 80386. Programs that use forms of memory locking specific to the 80286 may not execute properly when transported to a specific application of the 80386.

                                  Т.е. совместимость - это святое, но иногда можно и забить )))
                                  Ответить
                                  • показать все, что скрытоvanished
                                    Ответить
                                    • К слову о совместимости:

                                      Despite the fact that Pentium 4, Intel Xeon, and P6 family processors support processor ordering, Intel does not guarantee that future processors will support this model.

                                      Какой дисклеймер )))

                                      Т.е. интел внезапно может дропнуть гарантии про реордеринг. Но что-то мне намекает, что юзеры после этого дропнут интел.
                                      Ответить
                                      • показать все, что скрытоvanished
                                        Ответить
                                        • Они гарантируют (пока?) что даже без фенсов, лочек и т.п. у тебя чтения и записи почти не реордерятся.

                                          Насколько я помню, на x86 только read вперёд write может проскочить (если они в разные места, само собой). Всё остальное идёт строго как написано в коде. Ну смёржиться разве что могут если WB или WC режим.
                                          Ответить
                • XCHG — единственный пример такого хака?
                  Ответить
                  • показать все, что скрытоvanished
                    Ответить
                    • В инструкции к 80386 указана только инструкция XCHG.

                      11.2.2 Automatic Locking

                      In several instances, the processor itself initiates activity on the data
                      bus. To help ensure that such activities function correctly in
                      multiprocessor configurations, the processor automatically asserts the LOCK#
                      signal. These instances include:
                      • Acknowledging interrupts.
                      • Setting busy bit of TSS descriptor.
                      • Loading of descriptors.
                      • Updating page-table A and D bits.
                      • Executing XCHG instruction.
                      Ответить
                    • Кстати, а про костыль с mov ss знаешь же? Что он запрещает прерывания на следующую инструкцию.
                      Ответить
                      • показать все, что скрытоvanished
                        Ответить
                        • Чтобы ты мог написать

                          mov ss, ...
                          mov sp, ...

                          И наебнуться от прерывания между ними.
                          Ответить
                          • показать все, что скрытоvanished
                            Ответить
                            • Ну.
                              Ответить
                              • показать все, что скрытоvanished
                                Ответить
                                • показать все, что скрытоvanished
                                  Ответить
                                  • Ну да. А ещё языки оптимизируют под хуёвый код и хуёвые процы.
                                    Ответить
                                    • А ещё бывает такое:

                                      /*****************************************************************************\
                                          FUNCTION: CFtpDrop::CopyStorage
                                      
                                          DESCRIPTION:
                                              Copy a file contents provided as an IStorage.  Gack.
                                          We have to do this only because Exchange is a moron.
                                      
                                          Since there is no way to tell OLE to create a .doc file
                                          into an existing stream, we need to create the .doc file
                                          on disk, and then copy the file into the stream, then delete
                                          the .doc file.
                                      
                                          Note that CDropOperation::DoOperation() (_CopyOneHdrop) will do the ConfirmCopy
                                          and the FtpDropNotifyCreate(), too!  However, we want to fake
                                          it out and fool it into thinking we are doing a DROPEFFECT_COPY,
                                          so that it doesn't delete the "source" file.  *We* will delete
                                          the source file, because we created it.  (No need to tell the
                                          shell about disk size changes that don't affect it.)
                                      \*****************************************************************************/
                                      Ответить
                                      • показать все, что скрытоvanished
                                        Ответить
                                        • FILE: ftpdrop.cpp - IDropTarget interface


                                          Это расширение «Проводника», чтобы файл можно было мышкой кинуть на окошко, в котором отображается FTP. Почему они упомянули «Exchange», я не понял.
                                          Ответить
                                          • показать все, что скрытоvanished
                                            Ответить
                                            • В любом случае в исходниках «Винды» регулярно один отдел «Микрософта» обкладывает хуями другой за то, что приходится подставлять костыли.

                                              Где-то там были и патчи ОС под кривые прикладные программы.
                                              Ответить
                                              • показать все, что скрытоvanished
                                                Ответить
                                                • А кибербуллить можно?
                                                  Ответить
                                                  • показать все, что скрытоvanished
                                                    Ответить
                                                    • > какой-то пидарас сломал компиляцию

                                                      Да иногда просто не везёт. На той версии мастера, которая была перед мержом твоих изменений и запуском тестов всё было норм. Но за это время кто-то уже пролез в мастер. И всё сломалось.

                                                      Полную сериализацию коммитов никто в здравом уме делать не будет. Поэтому иногда shit happens.
                                                      Ответить
                                                      • показать все, что скрытоvanished
                                                        Ответить
                                                        • Это же не масштабируется...

                                                          Придётся тесты очень сильно поджимать по времени и вбрасывать кучу железа на их распараллеливание.

                                                          По-моему это не особо пригодно для большой команды.

                                                          И 100% багов это всё равно не поймает. Ну не сможешь ты всё-всё-всё протестировать в такой сериализованной модели.
                                                          Ответить
                                                          • показать все, что скрытоvanished
                                                            Ответить
                                                            • > никакие тесты не ловят 100 процентов

                                                              Ну вопрос в том, насколько полная сериализация коммитов ловит больше багов, чем более слабая но оптимистичная. И насколько она дороже обходится по железу и усилиям программистов.

                                                              Если была пара поломок мастера в месяц, то может и хуй с ними?

                                                              З.Ы. Я не против прекоммит тестов, я против их полной сериализации.
                                                              Ответить
                                • показать все, что скрытоvanished
                                  Ответить
        • показать все, что скрытоvanished
          Ответить
        • > компиляторы его умеют оптимизировать

          З.Ы. Беру свои слова обратно. gcc -O2 не осилило xor-swap оптимизнуть. А вот с std::swap весь твой extract_bits<uint32_t, uint32_t>(n, 0, 31) свернулся в одну ассемблерную инструкцию bswap.
          Ответить
          • показать все, что скрытоvanished
            Ответить
            • Не работает для типов, которые нельзя поксорить. По-моему этого уже достаточно.

              Ну и с наивным конпелятором, который не умеет в эту идиому, превращается в сраное говнище, которое не даёт процу распараллелить выполнение (в обычном swap'е по сути просто регистры заренеймятся).
              Ответить
              • показать все, что скрытоvanished
                Ответить
              • > Не работает для типов, которые нельзя поксорить.

                Скастовать в тип подходящего размера который ксорить можно, и потом поксорить (точнее, сделать указатель на такой-то тип с поддержкой xor, и присвоить в такие указатели адреса на переменные с неподходящим для перексориванием типы, и потом уже поксорить). Обменять float-ы так вполне можно.
                Ответить
                • показать все, что скрытоvanished
                  Ответить
                  • Через мемсру - нет. Остальное вроде всё UB.
                    Ответить
                • > Скастовать в тип подходящего размера

                  Сможешь это написать без ub'ов и короче наивного свапа?
                  Ответить
                  • Да, если вырубить стрикт алиазинг или использовать для указателей GNU-расширение __attribute ((may_alias))
                    Ответить
                    • > alias any other type of objects

                      Прощайте, оптимизации, мне будет вас не хватать. Хотя с char * в общем-то так же.
                      Ответить
                      • Если переменная с этим may_alias будет встречаться только в какой-то особой специальной функции обмена, разве это всюду сломает оптимизации?
                        Впрочем, xor swap вообще говоря говно, https://godbolt.org/z/9rT6ax оптимизируется он плохо. Компилятор не может в принципе породить точно такой же код 1 в 1 как при обмене переменных через третью, т.к. если мы меняем переменную саму с собой, xor-swap ее занулит
                        Ответить
                        • > если мы меняем переменную саму с собой, xor-swap ее занулит
                          Ну вот это и есть самое большое говнище в xor-swap. Всё прекрасно работает, а потом кто-нибудь передаёт туда уко-ко-козатели на одинаковые пельменные — и пиздец.
                          Ответить
                        • А на RISC процах xor-swap вообще не имеет никакого смысла. Инструкций для ксора с памятью то нету. Да и регистры экономить не надо.
                          Ответить
                • > сделать указатель на такой-то тип с поддержкой xor

                  Ну можно, только strict aliasing не забудь отключить.
                  Ответить
      • Еще есть нестандартные функции типа:
        uint16_t __builtin_bswap16 (uint16_t x)
        uint32_t __builtin_bswap32 (uint32_t x)
        uint64_t __builtin_bswap64 (uint64_t x)
        uint128_t __builtin_bswap128 (uint128_t x)
        из GCC, но Clang их вроде тоже умеет.

        У MSVC есть под это своя елда:
        https://docs.microsoft.com/ru-ru/cpp/c-runtime-library/reference/byteswap-uint64-byteswap-ulong-byteswap-ushort?view=vs-2019


        Еще есть интелевые интринсики:
        https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_bswap

        И более-менее стандартный htonl() ntohl() (В стандарте Си нет, но в POSIX есть).

        В крестоговно почему-то не завезли никакой специальной хуйни для разворота байтиков, но зато там можно функцию Бесселя считать.
        Ответить
        • Даже в бустоговно не завезли.
          Ответить
        • А в стандартах нету скорее всего из-за того, что представление числа стандартами не описывается. Хотя было бы неплохо иметь какие-нибудь шаблонные std::to_network_endian() и std::from_network_endian().
          Ответить
          • >А в стандартах нету скорее всего из-за того, что представление числа стандартами не описывается.

            Ну это не совсем так, в последнем стандарте C++ описывается, что представление signed чисел может быть только в "two's complement" (дополнительный код). http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html

            И если это не описываеся, почему б это не описать тогда?
            Ответить
            • Ну про endian там вроде ничего нет.

              С другой стороны, функции которые ты привёл - это такая же чистая математика как и функции бесселя. Можно и добавить, даже если на какой-нибудь платформе в духе PDP они не имеют никакого смысла.

              З.Ы. А потом захочется вращения чисел и поиска старшего бита.
              Ответить
              • > С другой стороны, функции которые ты привёл, это такая же чистая математика как и функции бесселя.

                Нет, пожалуй это ближе к области байтоебства, чем к математике. Зачем математикам разворачивать байтики?
                Ответить
                • Ну я в том смысле, что работа всех этих bswap'ов никак не зависит от платформы. Это просто такая функция над числом. Как и сдвиг или битовое И.
                  Ответить
                  • Ну вообще как-нибудь может и зависеть, например если байты не 8-битные.
                    Ответить
                    • Ну можно описать как перестановку элементов длиной CHAR_BIT, раз уж на то пошло...
                      Ответить
              • Кстати я так подозреваю, что two's complement для signed чисел сделали чтоб в constexpr поведение было более однозначным (не зависело от платформы). Оно-то всё еще зависит в какой-то мере, из-за implementation defined размеров всяких типов и хуйни с плавучими питухами (хотя может я тут ошибаюсь, и плавучепитуховые вычисления в коконстэкспрах всегда дают известно какой результат?).
                Ответить
                • Вполне возможно. А может быть чтобы убрать UB при касте из uint32_t в int32_t для больших чисел. Всё-таки очень много скверно написанных программ на это полагается.
                  Ответить
    • А если ссылку передать сюда?
      Ответить
      • Ну вообще можно и не ссылку передавать, а действовать через лямбда-захват, еще оптимальней будет.
        Ответить
        • У меня в Си никаких лямбда-захватов нет, и оптимальность от этого не страдает. Если функция заинлайнилась, никаких проблем нет.
          Ответить
          • Да я не знаю, чем там лямбда-захват оптимальнее будет... Код скорее всего одинаковый получится, как и с сишной функцией в общем-то.
            Ответить
      • Зачем? Зачем? Это же для целых чисел, их и без ссылок передать не зашквар.
        Ответить

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