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

    +15

    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
    25. 25
    26. 26
    27. 27
    28. 28
    29. 29
    30. 30
    31. 31
    32. 32
    33. 33
    34. 34
    35. 35
    36. 36
    37. 37
    38. 38
    39. 39
    40. 40
    41. 41
    42. 42
    43. 43
    44. 44
    45. 45
    46. 46
    47. 47
    48. 48
    49. 49
    50. 50
    51. 51
    52. 52
    53. 53
    54. 54
    55. 55
    56. 56
    57. 57
    58. 58
    59. 59
    60. 60
    61. 61
    62. 62
    63. 63
    64. 64
    65. 65
    66. 66
    67. 67
    68. 68
    69. 69
    70. 70
    71. 71
    72. 72
    73. 73
    74. 74
    75. 75
    76. 76
    /**
     * @brief Serializer generic interface.
     */
    template<typename ValueType>
    struct serializer
    {
        /**
         * @brief Parses value from raw bytes.
         * @param  from byte buffer parse value from
         */
        static ValueType parse(uint8_t *from);
    
        /**
         * @brief Writes value to raw byte buffer.
         * @param  value value to write
         * @param  dest destination buffer
         */
        static void write(ValueType value, uint8_t *dest);
    };
    
    template<>
    struct serializer<uint8_t>
    {
        static uint8_t parse(uint8_t *from)
        {
            return *from;
        }
        static void write(const uint8_t value, uint8_t *to)
        {
            *to = value;
        }
    };
    
    template<>
    struct serializer<uint16_t>
    {
        static uint16_t parse(uint8_t *from)
        {
            return (uint16_t)from[0] << 8 | from[1];
        }
        static void write(const uint16_t value, uint8_t *to)
        {
            to[0] = (value >> 8);
            to[1] = value & 0xff;
        }
    };
    
    template<>
    struct serializer<uint32_t>
    {
        static uint32_t parse(uint8_t *from)
        {
            return from[0] << 24 | from[1] << 16 | from[2] << 8 | from[3];
        }
        static void write(const uint32_t value, uint8_t *to)
        {
            serializer<uint16_t>::write(value >> 16, to);
            serializer<uint16_t>::write(value & 0xffff, to + 2);
        }
    };
    
    template<>
    struct serializer<uint64_t>
    {
        static uint64_t parse(uint8_t *from)
        {
            const uint32_t high = serializer<uint32_t>::parse(from);
            const uint32_t low = serializer<uint32_t>::parse(from + 4);
            return ((uint64_t) high << 32) | low;
        }
        static void write(const uint64_t value, uint8_t *to)
        {
            serializer<uint32_t>::write(value >> 32, to);
            serializer<uint32_t>::write(value & 0xffffffff, to + 4);
        }
    };

    Тут поднялась тема неуместного битолюбства... Решил поделиться одним из моих первых крестОпусов.
    "кроссплатформенный hton(sl)".

    Запостил: roman-kashitsyn, 22 Января 2013

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

    • то как сверху я тоже давно извращался. ни разу не удалось добится того что бы компилер это правильно распознал и в соответсвующую инструкцию сконвертил.

      искать пытаться не буду, но в прошлом находил крутой кусок дефайнов, который несколькими компиляторами распознавался как `ntoh[ls]()` и конвертился (по возможности) в соответствующую инструкцию проца. вот тот кусок дефайнов был настоящим джигитским говном.
      Ответить
      • компилятор заменяет хрень вида (a << 24) | ((a & 0xff00) << 8) | ((a & 0xff0000) >> 8) | (a >> 24) на bswap
        может просто на твоей платформе не было bswap?

        касательно темы
        1) гораздо лучше гонять value_type в value_type (получится вообще один метод), а не указатели в число и назад
        2) в обобщенной форме тоже несложно сделать, воспользовавшись std::reverse
        Ответить
        • В конечном счёте я ввёл перегруженные native_to_network и network_to_native, определяемые дефайнами в зависимости от платформы и избавился от serializer. Более подробно можно посмотреть здесь
          http://codereview.stackexchange.com/questions/20621/c-plain-type-serializer-design
          Ответить
          • симпатично сделал.

            я сам такое всегда на уровне С держу, что бы если надо макросами простую кодогенерацию делать. хотя это и с твоим пакетом тоже можно. (но я лично С++ с препроцессором пердпочитаю не мешать. дело вкуса.)

            к слову напомнило: http://en.wikipedia.org/wiki/Protocol_Buffers
            Ответить
          • очень сложно все даже в последнем варианте
            зачем бояться (uint8_t *)&value, он же reinterpret_cast<uint8_t *>(&value)?
            и не надо юнионов писать

            никаких проверок на переполнение в packet
            и немного попахивает злоупотреблением operator >>, но это уже субъективное

            совсем тонкости - только надеяться, что кроме целочисленных ValueType пользователь не догадается ничего присунуть в packet
            Ответить
            • > и не надо юнионов писать

              надо.

              http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
              Ответить
              • не надо
                ValueType result = arg;
                <swap result here>;
                return result;
                Ответить
              • ладно
                может я не до конца понимаю особенности сишного компилятора в этом плане
                поэкспериментировал с опциями O2, O3, fstrict-aliasing, fno-string-aliasing, ворнинга не получил даже на примере по твоей ссылке
                http://bit.ly/10q2dlB
                Ответить
              • В той статье есть подраздел Casting to char*
                It is always presumed that a char* may refer to an alias of any object. It is therefore quite safe, if perhaps a bit unoptimal (for architecture with wide loads and stores) to cast any pointer of any type to a char* type.
                uint32_t swap_words( uint32_t arg )
                {
                  char* const cp = (char*)&arg;
                  const char  c0 = cp[0];
                  const char  c1 = cp[1];
                  const char  c2 = cp[2];
                  const char  c3 = cp[3];
                
                  cp[0] = c2;
                  cp[1] = c3;
                  cp[2] = c0;
                  cp[3] = c1;
                
                  return (arg);
                }
                Т.е. судя по всему можно написать вот такой код, как и предлагал defecate-plusplus:
                template<typename ValueType>
                packet &operator>>(packet &p, ValueType &target)
                {
                    const std::size_t Size = sizeof(ValueType);
                    char *arr = (char*)&target;⌖
                    std::copy(p._head, p._head + Size, arr);
                    p.skip(Size);
                    target = network_to_native(target);
                    return p;
                }
                P.S. Неясно, действует ли это правило и на unsigned char *, поэтому и написал char * вместо uint8_t.
                Ответить
                • Лол, &target; превращается в ⌖. хтмл такой хтмл, движок ГК такой движок ГК.
                  Ответить
                  • &target &target &target

                    вот зачем дуров сломал стену, а страйкер эмодзи
                    Ответить
                    • Это общеизвестная проблема.
                      Никто не может совладать с дабл эскейпингом в вебе.

                      Тут вот какое дело: чтобы решить проблему дабл-эскейпинга, нужно иметь IQ, превышающий IQ гиббона минимум в два раза.

                      Но тот, чей IQ превышает IQ гиббона, не занимается веб-разработкой.

                      Получается как-бы замкнутый круг. В результате любой веб-сайт и любое веб-приложение всегда имеет проблему с double escape.

                      Это не только на ГК: на работе в Slack я наблюдаю тоже самое, а до этого я видел это в Salesforce, и даже в некоторым продуктах CI/CD.

                      Тот, кто решит эту проблему, воистину достоин нобелевской премии!
                      Ответить
            • > никаких проверок на переполнение в packet
              Это не часть публичной библиотеки (и пока не собирается ей стать), и размеры объектов проверяются перед записью. Я уже думал над добавлением нижней границы, возможно, ещё один указатель и лишняя проверка того стоят.

              > зачем бояться (uint8_t *)&value, он же reinterpret_cast<uint8_t *>(&value)?
              Я знаю, что можно, это скорее сишная привычка и выглядит безопаснее. Воможно, исправлю на reinterpret_cast.

              > кроме целочисленных ValueType пользователь не догадается ничего присунуть
              В том-то и дело, что если присунет - не скомпилится, не найдётся перегрузки.

              Радует, что родной говнокодик предоставляет гораздо более конструктивное code review, чем буржуйское поделие.
              Ответить
          • лол. коллеге послал - и он меня тут же носом тыкнул в мой же код. я такое на С++ делал. мельче и проще, но только еще извращенней: параметром темплейта было то что у тебя `uint8_t *` что бы можно было подсовывать итератор вектора.
            Ответить
        • > гораздо лучше гонять value_type в value_type (получится вообще один метод), а не указатели в число и назад
          Ну я так понимаю, Роман не ради htons через read+write этот код писал, а все-таки ради сериализации каких-нибудь объектов в массив байт и обратно.

          P.S. Интересно, скоро ли компилятор начнет оптимизировать код, который разбивает число циклом на байты, упихивает их в какой-нибудь вектор, вызывает std::reverse, и собирает число из вектора в... bswap? ;)
          Ответить
        • > может просто на твоей платформе не было bswap?

          скорее всего мои компиляторы "были" устаревшими сегодня. :)

          не про bswap речь. речь об инструкциях чтения из памяти с автоматическим bswap'ом. уж не помню как они зовутся. давно это было. и Intel, и PowerPC поддерживают такие инструкции.

          если есть желание ковырни линух на предмет /cpu_to_(be|le)|(be|le)_to_cpu/ - там эти инструкции используются.
          Ответить
          • линух? а что, htonl/htons уже недостаточно посикс?
            Ответить
            • я про ядро.

              в позиксных хидерах, htonl/htons это библиотечные функции. потому что должны быть совместимы со всем чем не попади. поэтому и функции. и значение уже должно быть вычитано из памяти и передано как параметр.

              в линухе, в ядре, эта группа специально платформенно-оптимизированых макросов/функций. там и можно увидеть в хидерах для интела/етц эти асмовые инструкции и/или трюки компилятора. включая свап при чтение из/запись в память и свап значения в регистре.
              Ответить
        • > в обобщенной форме тоже несложно сделать, воспользовавшись std::reverse
          template <class T> T swap(T val) {
              const int size = sizeof(T);
              char buf[size];
              std::copy((char*)&val, ((char*)&val) + size, buf);
              std::reverse(buf, buf + size);
              std::copy(buf, buf + size, (char*)&val);
              return val;
          }
          
          template <class T> T swap(T val) {
              std::reverse((char*)&val, ((char*)&val) + sizeof(T));
              return val;
          }
          компилятся в один и тот же цикл, который меняет байтики местами начиная с краев. До bswap в случае 32-битного инта gcc все-таки не допер, хотя в целом для говнища из первого моего примера - результат очень даже неплохой...
          Ответить
    • Любой сереализатор на крестах - не говно в виду отсутсвия более хорошего варианта.
      Ответить
    • - Илья, мне нужно кое-что тебе сказать. Я познакомилась с очень хорошим человеком, и он сделал мне предложение.
      Ответить

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