Расставим точки над структурами C/C++

    Недавно познакомился со структурами C/C++ — struct. Господи, да «что же с ними знакомиться» скажете вы? Тем самым вы допустите сразу 2 ошибки: во-первых я не Господи, а во вторых я тоже думал что структуры — они и в Африке структуры. А вот как оказалось и — нет. Я расскажу о нескольких жизненно-важных подробностях, которые кого-нибудь из читателей избавят от часовой отладки…




    Выравнивание полей в памяти


    Обратите внимание на структуру:

    struct Foo
    {
        char ch;
        int value;
    };

    Ну во-первых какой у этой структуры размер в памяти? sizeof(Foo)?
    Размер этой структуры в памяти зависит от настроек компилятора и от директив в вашем коде…

    В общем выравниваются в памяти поля по границе кратной своему же размеру. То есть 1-байтовые поля не выравниваются, 2-байтовые — выравниваются на чётные позиции, 4-байтовые — на позиции кратные четырём и т.д. В большинстве случаев (или просто предположим что сегодня это так) выравнивание размера структуры в памяти составляет 4 байта. Таким образом, sizeof(Foo) == 8. Где и как прилепятся лишние 3 байта? Если вы не знаете — ни за что не угадаете…

    • 1 байт: ch
    • 2 байт: пусто
    • 3 байт: пусто
    • 4 байт: пусто
    • 5 байт: value[0]
    • 6 байт: value[1]
    • 7 байт: value[2]
    • 8 байт: value[3]

    Посмотрим теперь размещение в памяти следующей структуры:

    struct Foo
    {
        char ch;
        short id;
        int value;
    };

    Оно выглядит вот так:

    • 1 байт: ch
    • 2 байт: пусто
    • 3 байт: id[0]
    • 4 байт: id[1]
    • 5 байт: value[0]
    • 6 байт: value[1]
    • 7 байт: value[2]
    • 8 байт: value[3]

    То есть, то что можно впихнуть до выравнивания по 4 байта — впихивается на ура (без увеличения размера структуры в памяти), добавим ещё одно поле:

    struct Foo
    {
        char ch;
        short id;
        short opt;
        int value;
    };

    Посмотрим на размещение полей в памяти:

    •   1 байт: ch
    •   2 байт: пусто
    •   3 байт: id[0]
    •   4 байт: id[1]
    •   5 байт: opt[0]
    •   6 байт: opt[1]
    •   7 байт: пусто
    •   8 байт: пусто
    •   9 байт: value[0]
    • 10 байт: value[1]
    • 11 байт: value[2]
    • 12 байт: value[3]

    Всё это ой как печально, но есть способ бороться с этим прямо из кода:

    #pragma pack(push, 1)
    struct Foo
    {
        // ...
    };
    #pragma pack(pop)

    Мы установили размер выравнивания в 1 байт, описали структуру и вернули предыдущую настройку. Возвращать предыдущую настройку — категорически рекомендую. Иначе всё может закончиться очень плачевно. У меня один раз такое было — падало Qt. Где-то заинклюдил их .h-ник ниже своего .h-ника…

    Битовые поля


    В комментариях мне указали на то, что битовые поля в структурах по стандарту являются «implementation defined» — потому их использования лучше избежать, но для меня соблазн слишком велик...

    Мне становится не то что неспокойно на душе, а вообще становится хреново, когда я вижу в коде заполнение битовых полей при помощи масок и сдвигов, например так:

    unsigned field = 0x00530000;
    
    // ...
    
    field &= 0xFFFF00FF;
    field |= (id) << 8;
    
    // ...
    
    field &= 0xFFFFFF83;
    field |= (proto) << 2;

    Всё это пахнет такой печалью и такими ошибками и их отладкой, что у меня сразу же начинается мигрень! И тут из-за кулис выходят они — Битовые Поля. Что самое удивительное — были они ещё в языке C, но кого ни спрашиваю — все в первый раз о них слышат. Этот беспредел надо исправлять. Теперь буду давать им всем ссылку, ну или хотя бы ссылку на эту статью.

    Как вам такой кусок кода:

    #pragma pack(push,1)
    struct IpHeader
    {
        uint8_t header_length:4;
        uint8_t version:4;
        uint8_t type_of_service;
        uint16_t total_length;
        uint16_t identificator;
    
        // Flags
        uint8_t _reserved:1;
        uint8_t dont_fragment:1;
        uint8_t more_fragments:1;
    
        uint8_t fragment_offset_part1:5;
        uint8_t fragment_offset_part2;
        uint8_t time_to_live;
        uint8_t protocol;
        uint16_t checksum;
    
        // ...
    };
    #pragma pack(pop)

    А дальше в коде мы можем работать с полями как и всегда работаем с полями в C/C++. Всю работу по сдвигам и т.д. берет на себя компилятор. Конечно же есть некоторые ограничения… Когда вы перечисляете несколько битовых полей подряд, относящихся к одному физическому полю (я имею ввиду тип который стоит слева от имени битового поля) — указывайте имена для всех битов до конца поля, иначе доступа к этим битам у вас не будет, иными словами кодом:

    #pragma pack(push,1)
    stuct MyBitStruct
    {
        uint16_t a:4;
        uint16_t b:4;
        uint16_t c;
    };
    #pragma pack(pop)

    Получилась структура на 4 байта! Две половины первого байта — это поля a и b. Второй байт не доступен по имени и последние 2 байта доступны по имени c. Это очень опасный момент. После того как описали структуру с битовыми полями обязательно проверьте её sizeof!

    Также порядок размещения битовых болей в байте зависит от порядка байтов. При порядке LITTLE_ENDIAN битовые поля раздаются начиная со первых байтов, при BIG_ENDIAN — наоборот…

    Порядок байтов


    Меня также печалят в коде вызовы функций htons(), ntohs(), htonl(), nthol() в коде на C++. На C это ещё допустимо, но не на С++. С этим я никогда не смирюсь! Внимание всё нижесказанное относится к C++!

    Ну тут я буду краток. Я в одной из своих предыдущих статей уже писал что нужно делать с порядками байтов. Есть возможность описать структуры, которые внешне работают как числа, а внутри сами определяют порядок хранения в байтах. Таким образом наша структура IP-заголовка будет выглядеть так:

    #pragma pack(push,1)
    struct IpHeader
    {
        uint8_t header_length:4;
        uint8_t version:4;
        uint8_t type_of_service;
        u16be total_length;
        u16be identificator;
    
        // Flags
        uint8_t _reserved:1;
        uint8_t dont_fragment:1;
        uint8_t more_fragments:1;
    
        uint8_t fragment_offset_part1:5;
        uint8_t fragment_offset_part2;
        uint8_t time_to_live;
        uint8_t protocol;
        u16be checksum;
    
        // ...
    };
    #pragma pack(pop)

    Внимание собственно обращать на типы 2-байтовых полей — u16be. Теперь поля структуры не нуждаются ни в каких преобразованиях порядка байт. Остаются проблемы с fragment_offset, ну а у кого их нет — проблем-то. Тем не менее тоже можно придумать шаблон, прячущий это безобразие, один раз его оттестировать и смело использовать во всём своём коде.

    «Язык С++ достаточно сложен, чтобы позволить нам писать на нём просто» © Как ни странно — Я

    З.Ы. Планирую в одной из следующих статей выложить идеальные, с моей точки зрения, структуры для работы с заголовками протоколов стека TCP/IP. Отговорите — пока не поздно!
    Поделиться публикацией

    Похожие публикации

    Комментарии 81
      +4
      Еще стоит рассказать о разнице между структурами в C и С++.
        +2
        Не думаю, что это нужно тем кто пишет на C++.
        Действительно, зачем? Если только для общего развития…
          +4
          То, что описано в этой заметке в большей степени востребовано программистами на С, чем на С++. Драйверы и низкоуровневые библиотеки стараются писать на С. А раз в заголовок вынесено С++, то, наверное, имеет смысл рассказать о том насколько усовершенствовались (приблизились к объектам, получили дополнительный функционал) структуры в С++.
            +2
            Боюсь, тогда выйдет заметка о классах и ООП) Сегодня уже поздно. Завтра тогда добавлю раздел чисто с отличиями структур Сишных от Плюсовых.
          +1
          Это же Капитанство. В С++ структуры или POD типы или же классы, но с public доступом по умолчанию. Причем последние получаются если в структурах задавать методы особенно виртуальные или наследовать их друг от друга.
            +1
            Ага, да, если бы всё было так просто (некоторые члены комитета стандартизации даже не помнят наизусть всех правил POD'ности). В C++11 даже standard layout структуры ввели, так как POD сочетает слишком много случаев использования, а концепция одна.
              0
              Ну не наследованная ни от чего структура с методами вполне себе PODнутая остается по способу хранения в памяти. Да и наследованная полиморфная вполне может такой быть ибо информацию о наследовании в рантайме спокойно можно выкинуть.
          +8
          только, наверное, следует сказать, что упакованные структуры работают медленнее, чем неупакованные.

          операции с отдельными битовыми полями работают медленнее, чем маски.

          u16be вообще что-то очень нестандартное.
            +1
            > упакованные структуры работают медленнее, чем неупакованные.
            Да, так как не все процессоры поддерживают обращение к невыровненным данным.

            > операции с отдельными битовыми полями работают медленнее, чем маски.
            А это почему?
              +3
              Может так получится, что для чтения одного поля вам потребуется прочитать два машинных слова, если структуры выровнены на машинное слово, то такого не будет.
              +2
              операции с отдельными битовыми полями работают медленнее, чем маски.

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

              «Некоторые люди утверждают, что второй пример
              лучше, чем битовое поле, потому что здесь нет неявного сдвига, но
              многие машины поддерживают команду проверки бита, которая устраняет
              какую-либо потребность в сдвиге, который в случае своего использования
              вызывает очень незначительные накладные расходы. Необходимость в
              устранении ненужной путаницы обычно перевешивает подобные
              соображения о снижении эффективности.»
              +3
              У вас ошибка в расположении padding bytes для второй и третьей структуры.
                0
                Спасибо, поправил.
                +4
                Мне всегда было интересно, зачем менять выравнивание структур. Оно же не зря установлено компилятором для платформы.
                  +12
                  Ну как зачем, чтобы напрямую читать из сети или файла бинарные блобы в структуры вместо того, чтобы написать нормальный парсинг.
                    0
                    ага, а потом нарваться в самый неожиданный момент на LE / BE
                    +15
                    Некоторые считают, что так они показывают свой высокий профессионализм и знания.
                      –1
                      Причины могут быть разные. Компилятором установлено значение по умолчанию, что не значит, что оно в любой ситуации будет оптимальным. Кроме того, не гарантируется что оно будет одинаковое для всех компиляторов даже под данную платформу, а значит для интерфейсов нужно явно указывать выравнивание.
                        0
                        > не гарантируется что оно будет одинаковое для всех компиляторов даже под данную платформу, а значит для интерфейсов нужно явно указывать выравнивание.

                        В пределах одного ABI будет одинаковое, а больше ни для какой практической цели не требуется.
                          0
                          В идеальном мире наверное так и есть. А в реальном — поделитесь ссылкой на список ABI под все платформы? (начиная с windows)
                            0
                            Зачем вам список ABI? Если не совпадают ABI двух модулей вы в первую очередь не сможете вызвать функции/методы ваших межмодульных интерфейсов да хотя бы из-за разницы в name mangling или способа передачи аргументов.
                              0
                              Под windows для этого есть стандарты типа stdcall и т.п., но они не задают выравнивание.
                          +1
                          За каждую pragma в коде нужно его автора на день сажать в котел с кипящим маслом.
                          +1
                          Столкнулся с этим недавно, когда пытался кое-какой клиент скомпилировать на 64-битной платформе. Без выравнивания ничего не работало.
                          +9
                          Да, кстати, для информации: «The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined.»

                          То есть гарантировать, что битовые поля всегда будут на одном и том же месте — нельзя. С другим компилятором/платформой всё может перестать работать, а виноват в этом будет не компилятор.
                            +2
                            Как уже написал выше qehgt у Вас ошибка для второй и третей структур, так как short подавляющее большинство компиляторов выравнивают на границу 2-х байтов. Что касается битовых полей, то с ними вообще все сложно. Например, MS VC (и не только он) выравнивает размеры битовых полей описанных как int и unsigned int на 4 байта даже если в структуре одно поле из одного бита, а использование для описания битовых полей всяких short и прочих модификаторов вообще не стандартно и implementation defined.
                            Стандарт не определяет:
                            — The alignment of the addressable storage unit allocated to hold a bit-field
                            И отдает на откуп компиляторам:
                            — Whether a ‘‘plain’’ int bit-field is treated as a signed int bit-field or as an
                            unsigned int bit-field
                            — Allowable bit-field types other than _Bool, signed int, and unsigned int
                            — Whether atomic types are permitted for bit-fields
                            — Whether a bit-field can straddle a storage-unit boundary
                            — The order of allocation of bit-fields within a unit
                            — The alignment of non-bit-field members of structures. This should present
                            no problem unless binary data written by one implementation is read by another.
                              0
                              P.S. Это был комментарий автору исходного сообщения :)
                            +13
                            Всё это ой как печально, но есть способ бороться с этим прямо из кода:
                            #pragma pack(push, 1)

                            Ох уж эти виндузятники, вам уже впрям писать библию «нету дрогой ОС кроме виндоуз и другого компилятора, кроме vc». :) Это, я к тому, что используя платфоро-зависимые фичи и пропраятные расширения языка, стоит как минимум на это указывать.

                            Кстати, хотя #pragma pack это и майкрософтовская примочка, в gcc можно включит ее поддержку и использовать вместо родной __attribute__ ((__packed__))
                              0
                              Странно, у меня на древней версии MinGW прагма пак работала без проблем.
                              +6
                              Ещё стоит вспомнить, что битовые поля — это thread unsafe штука, про что обычно забывают, и, в отличие от обычных полей структуры, независимо обновлять их из потоков уже не получится.
                                +5
                                Ну безопастно обновлять поля структуры можно только со времен c++11, до него битовые поля и поля структуры на одной линии огня. После c++11 проблема осталась только с полями, да.
                                  0
                                  *безопасно
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  +2
                                  Примечание для тех, кого заботит размер структур. Анализатор PVS-Studio позволяет обнаруживать структуры с неоптимальным расположением полей: V802. On 32-bit/64-bit platform, structure size can be reduced from N to K bytes by rearranging the fields according to their sizes in decreasing order.
                                    0
                                    В разделе «Порядок байт» в коде скорее всего будут проблемы на LE :) Т.е. на в т.ч. x86… в старших битах первого байта там будет header_length, а в младших ver. Тогда как на BE наоборот, т.е. так же как и должно быть в IP пакете.
                                    Собственно во многих ОС можно посмотреть netinet/ip.h, как там описан заголовок IP пакета. Так что битовые поля тоже достаточно платформозависимые (об этом правда уже написали выше), в отличии кстати от логических операторов ;) Конечно если для них изначально подготовить данные.
                                      +13
                                      Это всё, конечно, хорошо. Но, как уже правильно отметили в комментариях, непереносимо, а на некоторых платформах и попросту небезопасно. Из лично известных мне — SPARC весьма щепетильно относится к выравниванию данных, и генерит исключение SIGBUS в случае доступа к неправильно выравненным данным.
                                      Спасаться от лишнего паддинга можно и без pragma pack. Рецепт прост — поля в структуре «сортируются» по размеру. В начале помещаются самые большие (uint64, uint32), в конце — самые маленькие — (uint8). Все вопросы с порядком байт должны (на мой взгляд) решаться в точке сериализации и десериализации данных (ну, например, при отправке в сеть и получения из сети), а сам этот процесс, по хорошему, должен производиться не сразу в структуру, а путём последовательного чтения данных из буфера и размещения в соответствующих полях в специфичном для платформы виде. Может быть это не так «просто» и «изящно» выглядит, зато надёжно, переносимо и расширяемо.
                                        +5
                                        Когда Вы делаете #pragma pack(push, 1), Вы выигрываете несколько байтов и проигрываете несколько тактов процессора (процессор default архитектуры не станет работать с int32_t, чей адрес не выравнен на границу 4-х). Имхо, нет смысла этого делать, пока Вам не надо по какой-то причине ужать размер структуры. И даже если Вам таки надо ужать структуру, то, ИМХО, лучше руками перетасовать поля структуры, чем делать pragma pack.
                                          –1
                                          Данная функциональность нужна не для выигрыша тактов процессора, а чтобы загрузить в память байты, а потом «приложить» к ним структуру. Так можно прочитать пару мегабайт из файла и преобразовать к структуре/структурам без того чтобы парсить все их байты и заполнять поля структур по одной. Результат — очень быстрая загрузка контента файла(или данных из сети итд) прямо в структурированный вид.
                                            +2
                                            1. С помощью pragma pack Вы проигрываете такты процессора.
                                            2. Без pragma pack тоже можно выделить нужное количество памяти и прочитать в нее структуру из файла, если приложение компилируется с одними и теми же настройками компилятора, одним и тем же компилятором. Если приложение компилируется в несколько либ, с разными настройками выравнивания, то чтения из файла это меньшая из Ваших проблем.

                                            Использовать pragma pack, ИМХО, есть смысл использовать только в двух случаях:
                                            1. Вы храните очень много данных в файле и размер жесткого диска и/или оперативной памяти сильно ограничены. В таком случае, скорее всего, процессорное время тоже очень дорого, по этому тут классический трейдоф между памятью и процессорным временем.
                                            2. У Вас несколько версий программы, которые компилируется с разными настройками выравнивания. В таком случае, ИМХО, лучше использовать сериализацию, как писал FlexFerrum.
                                              +1
                                              3. (last but not least) вы работаете с регистрами оборудования
                                              +2
                                              Операция загрузки «пары мегабайт» как правило однократна. Ее производительность при этом маловажная вещь, ее можно не оптимизировать вообще.
                                              А вот потом, при обработке этих данных, адресовать массив структур, который на 4 не делится будет более чем затратно.

                                              имхо это плохое решение выиграть N милисекунд там где это неважно, чтобы потом проиграть N процентов там где это важно.
                                              –3
                                              Ааааарррргх. Если процессор не может работать с невыровненными данными, значит компилятор, скорее всего, раскидает это на много команд чтения-сдвига-слияния.
                                              И откуда вы (и многие другие комментаторы) взяли вообще, что программист волен раскидывать данные как хочет? Есть протокол обмена, есть заголовки, их хочется красиво разбирать (да и в код глянуть быстрее, чем искать стандарт и назначения полей). Я согласен, что стандарты обычно пишут красиво, но 24-битные числа тоже бывают.
                                                +2
                                                >>Когда Вы делаете #pragma pack(push, 1), Вы выигрываете несколько байтов и проигрываете несколько тактов процессора
                                                >Ааааарррргх. Если процессор не может работать с невыровненными данными, значит компилятор, скорее всего, раскидает это на много команд чтения-сдвига-слияния.

                                                Я про это и написал. О чем Вы пытаетесь спорить?

                                                >И откуда вы (и многие другие комментаторы) взяли вообще, что программист волен раскидывать данные как хочет?
                                                Это С++, детка, здесь могут и pragma pack написать, и delete this. Язык разрабатывался в расчете на то, что программист лучше знает. По этому, он дает возможность выстрелить себе в ногу. По этому, он дает возможность писать очень эффективный код для конкретного железа.
                                                  –3
                                                  Нет, моё «Ааааарррргх ...» было к «процессор default архитектуры не станет работать с int32_t, чей адрес не выравнен на границу 4-х».

                                                  >Это С++, детка
                                                  Вот яркий пример: ru.wikipedia.org/wiki/%D0%9F%D0%B0%D0%BA%D0%B5%D1%82_IPv6#.D0.A4.D0.B8.D0.BA.D1.81.D0.B8.D1.80.D0.BE.D0.B2.D0.B0.D0.BD.D0.BD.D1.8B.D0.B9_.D0.B7.D0.B0.D0.B3.D0.BE.D0.BB.D0.BE.D0.B2.D0.BE.D0.BA. Расскажите авторам про то, что в своей структуре поменяете местами Flow Label и Traffic Class, т.к. так будет быстрее.
                                                    +2
                                                    Не надо путать протоколы обмена данными с представлением данных в памяти конкретной программы, работающей на конкретной архитектуре.
                                                      +2
                                                      Я перестал понимать, какую точку зрения Вы отстаиваете:
                                                      — pragma pack благо и его надо использовать всегда
                                                      — pragma pack вред и его нельзя использовать никогда
                                                      — pragma pack вред, но иногда его можно использовать
                                                      — pragma pack благо, но иногда лучше обойтись без него
                                                      — все любят котиков
                                                        0
                                                        — Единая Россия.

                                                        Я отстаиваю точку зрения, что раз человек использует pragma pack, значит ему это надо. И он представляет, к чему это приведёт.
                                                    +2
                                                    А если не раскидает? Да и вообще, с какой стати компилятор должен раскидывать доступ к таким данным? Нет, может быть за последние 6 лет что-то и изменилось, но в середине 2000-ых сановские компиляторы таким точно не страдали.
                                                    Протокол обмена — это, обычно, хорошо стандартизированная и описанная штука. И данные, которые ходят по этому протоколу, совсем не обязательно должны «ложиться» на их бинарное представление в памяти. Более того, то, что они в данном конкретном случае ложатся — это большое везение. :) По этому при получении данных их таки лучше разобрать «ручками», и разложить по полям структуры, чем играть в рулетку и надеяться на то, что авось повезёт, и данные в память лягут так, как надо.
                                                      –2
                                                      В моём случае повезло (вместе с первоначальным копированием из устройства), а универсальности не существует: всегда найдётся что-нибудь, что разрушит стройность. Вот я столкнулся с тем, что через PCI плохо записывать в 1 устройство меньше 4 байт (занулит остальное), а из другого читать больше 2 байт (тоже занулит). И да, в таком случае уже полагаться на компилятор не получается.
                                                      А вообще, ситуаций бесконечно много и в каждой свои методы удобнее. Можно лишь описать инструменты и *ожидаемую разработчиком* область применения.
                                                        +4
                                                        Об том и речь. А по поводу «ожидаемой области применения»… У меня сложилось впечатление, что автор статьи сам слабо себе представляет цену тех советов, которые даёт. Такое впечатление сложилось вот почему. В первой части (про pragma pack) резюме такое: «Если вам мешает паддинг, выкиньте его с помощью прагмы!». И ни слова о том, зачем паддинг нужен, и почему от него надо избавляться только в самом крайнем случае. Из содержимого третьей части можно сделать вывод, что автор предлагает читать заголовок TCP/IP-пакета сразу в предложенную структуру. При этом нет ни слова о том, что, вообще говоря, структура эта не является POD-типом (потому что в ней присутствуют не-POD типы — те самые u16be), и такие манипуляции с ней не рекомендуются. Таким образом, в случае компиляции всего этого дела одним конкретным компилятором (Visual C++) может работать, а другом — нет.
                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                      +4
                                                      То есть вот никаких других способов нет вообще, так?
                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                    0
                                                    Насколько я помню, у битовых полей наблюдались проблемы с совместимостью, как между компиляторами, так и архитектурами.

                                                    Хотя было бы приятно узнать, что я неправ.
                                                      0
                                                      довольно жесткий стандарт для индустриального ПО — MISRA — битовые поля ограничивает: RULE 111: Bit fields shall only be defined to be one of type unsigned int or signed int. Все остальное считается «implementation-defined type».

                                                      Часто для критических приложений использование битовых полей просто запрещают.
                                                        0
                                                        Ну, собственно, если для адекватного понимания конструкции в продакшен-коде требуется хорошенько покопаться в стандарте (или использование этой конструкции ведёт к UB/ID), то лучше такую конструкцию не использовать.
                                                          0
                                                          Ну, это не обязательно, портабельный код нужен не везде, даже для частично портабельного прецедент и читабельность важнее, ex:

                                                          $ grep -r "__packed" /usr/src/linux | wc -l
                                                          9075
                                                            0
                                                            Да я и не говорил, что исключений не бывает. :)
                                                      +2
                                                      У меня сразу простой вопрос, как с переносимостью кода на другие архитектуры, не x86? ARM (седьмой и девятый), MIPS например?

                                                        –1
                                                        за ARM9 и MIPS не скажу, но ARM7, емнип, только выравненные по 4-байтовой границе данные понимает. Cortex — побайтово
                                                          –1
                                                          Это больше к компилятору вопрос, умеет ли он нормально разруливать особенности архитектуры. GCC, например, глючил для SPARC, как уже выше писали (сейчас может исправился).
                                                            0
                                                            Выше писали про SunCC (SunCC 5.5, если быть совсем точным) :)
                                                            0
                                                            если схемотехника камня физически не позволяет адресовать отдельные байты, никакие фишки компилятора тут не помогут
                                                              0
                                                              смотря что вы имеете в виду

                                                              реализовать обращение к невыровненному байтовому полю через, скажем, 32-битный read-modify-write компилятор может без особого труда, согласны?
                                                                +1
                                                                Согласен. Я как раз о том, что если проц не имеет инструкции «загрузить байт по адресу… в регистр» или имеет, но адрес при этом должен быть кратным адресу слова (как раз вариант ARM7), то для манипуляции с байтом компилятору придется грузить все слово и использовать маски
                                                        +4
                                                        Планирую в одной из следующих статей выложить идеальные, с моей точки зрения, структуры для работы с заголовками протоколов стека TCP/IP

                                                        Давай. А мы посмотрим, как оно заработает на PowerPC
                                                          0
                                                          Для начало надо будет посмотреть, как оно заработает на x86 :) Т.к. описанная в статье структура IP пакета соответствует IP протоколу как раз на процессорах с big endian, а на LE будет отличаться.
                                                            0
                                                            У меня она работает на LittleEndian. Нижняя структура, там где u16be
                                                              +2
                                                              И какой же результат будет у этого кода?
                                                              IpHeader iphdr = {0};
                                                              iphdr.version = 4;
                                                              std::cou t<< std::hex << std::showbase << (int)*(uint8_t *)&iphdr << std::endl;
                                                              

                                                              Почему в последних 4х битах первого байта вдруг оказалась версия IP протокола, когда она должна быть в первых 4х?
                                                                –1
                                                                Можно увидеть ваш вывод? И назовите компилятор и платформу, пожалуйста.
                                                                  +1
                                                                  В коде лишний пробел случайно поставил :)
                                                                  А так вывод 0х4. Платформа например x86_64, компилятор gcc version 4.6.3 (Debian 4.6.3-1). Или специально проверил на под Windows x86, компилятор «Microsoft ® 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86» (правда пришлось stdint.h отсюда подсунуть msinttypes.googlecode.com/svn/trunk/stdint.h). Результаты идентичные. Да и собственно я же не просто так про это написал, выше уже писал про netinet/ip.h. В моем дистрибутиве в нем есть такие строчки:
                                                                  struct iphdr
                                                                    {
                                                                  #if __BYTE_ORDER == __LITTLE_ENDIAN
                                                                      unsigned int ihl:4;
                                                                      unsigned int version:4;
                                                                  #elif __BYTE_ORDER == __BIG_ENDIAN
                                                                      unsigned int version:4;
                                                                      unsigned int ihl:4;
                                                                  #else
                                                                  # error "Please fix <bits/endian.h>"
                                                                  #endif
                                                                  

                                                                  Под другими ОС, которые поддерживают как LE так и BE платформы, и где такой файл в наличии, так же присутствует нечто подобное.
                                                                    0
                                                                    Спасибо, добавлю в статью что порядок выделения бит тоже зависит от порядка байт. Я действительно перепутал порядок. Надеюсь вскоре опубликовать статью с универсальными заголовками. Попытаюсь шаблонизировать битовые поля…
                                                                      0
                                                                      Я сказал только про первые два битовых поля, но далее то там есть еще, для флагов и смещения…
                                                                        0
                                                                        А может быть не стоит? В чём профит?
                                                            +1
                                                            Хотелось бы услышать про магию u16be. Никогда не сталкивался.
                                                              0
                                                              Там ссылка на другую статью стоит
                                                              +1
                                                              А чем не нравится htons(), ntohs(), htonl(), nthol(), да еще и на C++? Нужно изобрести свой велосипед по перетасовке бит?
                                                                –1
                                                                Не нравится тем, что можно забыть это сделать. А обёрнутое классом — не надо помнить.
                                                                  –1
                                                                  по перестановке байт, если быть точным
                                                                  0
                                                                  в свое время убил на это целый рабочий день — считывался поток данных по uart, а потом на считанный массив накладывался указатель на структуру и поидее сразу можно было таким образом удобно работать со считанными данными, но из за выравнивания после накладывания структуры — оказывался в полях какой то мусор — получалось — смотрю на структуру — вроде по размерам полей и порядку все сходится, смотрю на массив — вроде тоже, а вот в скомпилированном виде уже нифига не сходилось

                                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                                  Самое читаемое