Comments 82
Еще стоит рассказать о разнице между структурами в C и С++.
+4
Не думаю, что это нужно тем кто пишет на C++.
Действительно, зачем? Если только для общего развития…
Действительно, зачем? Если только для общего развития…
+2
То, что описано в этой заметке в большей степени востребовано программистами на С, чем на С++. Драйверы и низкоуровневые библиотеки стараются писать на С. А раз в заголовок вынесено С++, то, наверное, имеет смысл рассказать о том насколько усовершенствовались (приблизились к объектам, получили дополнительный функционал) структуры в С++.
+4
Это же Капитанство. В С++ структуры или POD типы или же классы, но с public доступом по умолчанию. Причем последние получаются если в структурах задавать методы особенно виртуальные или наследовать их друг от друга.
+1
Ага, да, если бы всё было так просто (некоторые члены комитета стандартизации даже не помнят наизусть всех правил POD'ности). В C++11 даже standard layout структуры ввели, так как POD сочетает слишком много случаев использования, а концепция одна.
+1
только, наверное, следует сказать, что упакованные структуры работают медленнее, чем неупакованные.
операции с отдельными битовыми полями работают медленнее, чем маски.
u16be вообще что-то очень нестандартное.
операции с отдельными битовыми полями работают медленнее, чем маски.
u16be вообще что-то очень нестандартное.
+8
> упакованные структуры работают медленнее, чем неупакованные.
Да, так как не все процессоры поддерживают обращение к невыровненным данным.
> операции с отдельными битовыми полями работают медленнее, чем маски.
А это почему?
Да, так как не все процессоры поддерживают обращение к невыровненным данным.
> операции с отдельными битовыми полями работают медленнее, чем маски.
А это почему?
+1
операции с отдельными битовыми полями работают медленнее, чем маски.
Не факт, в небезызвестной книге «Веревка достаточной длины...», автор рекомендует использовать битовые поля:
«Некоторые люди утверждают, что второй пример
лучше, чем битовое поле, потому что здесь нет неявного сдвига, но
многие машины поддерживают команду проверки бита, которая устраняет
какую-либо потребность в сдвиге, который в случае своего использования
вызывает очень незначительные накладные расходы. Необходимость в
устранении ненужной путаницы обычно перевешивает подобные
соображения о снижении эффективности.»
+2
У вас ошибка в расположении padding bytes для второй и третьей структуры.
+3
Мне всегда было интересно, зачем менять выравнивание структур. Оно же не зря установлено компилятором для платформы.
+4
Ну как зачем, чтобы напрямую читать из сети или файла бинарные блобы в структуры вместо того, чтобы написать нормальный парсинг.
+12
Некоторые считают, что так они показывают свой высокий профессионализм и знания.
+15
Причины могут быть разные. Компилятором установлено значение по умолчанию, что не значит, что оно в любой ситуации будет оптимальным. Кроме того, не гарантируется что оно будет одинаковое для всех компиляторов даже под данную платформу, а значит для интерфейсов нужно явно указывать выравнивание.
-1
> не гарантируется что оно будет одинаковое для всех компиляторов даже под данную платформу, а значит для интерфейсов нужно явно указывать выравнивание.
В пределах одного ABI будет одинаковое, а больше ни для какой практической цели не требуется.
В пределах одного ABI будет одинаковое, а больше ни для какой практической цели не требуется.
0
В идеальном мире наверное так и есть. А в реальном — поделитесь ссылкой на список ABI под все платформы? (начиная с windows)
0
Зачем вам список ABI? Если не совпадают ABI двух модулей вы в первую очередь не сможете вызвать функции/методы ваших межмодульных интерфейсов да хотя бы из-за разницы в name mangling или способа передачи аргументов.
0
За каждую pragma в коде нужно его автора на день сажать в котел с кипящим маслом.
+1
Столкнулся с этим недавно, когда пытался кое-какой клиент скомпилировать на 64-битной платформе. Без выравнивания ничего не работало.
+1
Если работаете с железом — иногда бывают такие структуры где порядок поменять нельзя, а некоторые поля меньшего размера располагаются выше полей большего размера из-за чего компилятор может добавить выравнивание которое вас не обрадует. (Из примеров — дескрипторные таблицы x86)
0
Да, кстати, для информации: «The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined.»
То есть гарантировать, что битовые поля всегда будут на одном и том же месте — нельзя. С другим компилятором/платформой всё может перестать работать, а виноват в этом будет не компилятор.
То есть гарантировать, что битовые поля всегда будут на одном и том же месте — нельзя. С другим компилятором/платформой всё может перестать работать, а виноват в этом будет не компилятор.
+9
Как уже написал выше 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.
Стандарт не определяет:
— 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.
+2
Всё это ой как печально, но есть способ бороться с этим прямо из кода:
#pragma pack(push, 1)
Ох уж эти виндузятники, вам уже впрям писать библию «нету дрогой ОС кроме виндоуз и другого компилятора, кроме vc». :) Это, я к тому, что используя платфоро-зависимые фичи и пропраятные расширения языка, стоит как минимум на это указывать.
Кстати, хотя #pragma pack это и майкрософтовская примочка, в gcc можно включит ее поддержку и использовать вместо родной __attribute__ ((__packed__))
#pragma pack(push, 1)
Ох уж эти виндузятники, вам уже впрям писать библию «нету дрогой ОС кроме виндоуз и другого компилятора, кроме vc». :) Это, я к тому, что используя платфоро-зависимые фичи и пропраятные расширения языка, стоит как минимум на это указывать.
Кстати, хотя #pragma pack это и майкрософтовская примочка, в gcc можно включит ее поддержку и использовать вместо родной __attribute__ ((__packed__))
+13
Ещё стоит вспомнить, что битовые поля — это thread unsafe штука, про что обычно забывают, и, в отличие от обычных полей структуры, независимо обновлять их из потоков уже не получится.
+6
Примечание для тех, кого заботит размер структур. Анализатор 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.
+2
В разделе «Порядок байт» в коде скорее всего будут проблемы на LE :) Т.е. на в т.ч. x86… в старших битах первого байта там будет header_length, а в младших ver. Тогда как на BE наоборот, т.е. так же как и должно быть в IP пакете.
Собственно во многих ОС можно посмотреть netinet/ip.h, как там описан заголовок IP пакета. Так что битовые поля тоже достаточно платформозависимые (об этом правда уже написали выше), в отличии кстати от логических операторов ;) Конечно если для них изначально подготовить данные.
Собственно во многих ОС можно посмотреть netinet/ip.h, как там описан заголовок IP пакета. Так что битовые поля тоже достаточно платформозависимые (об этом правда уже написали выше), в отличии кстати от логических операторов ;) Конечно если для них изначально подготовить данные.
0
Это всё, конечно, хорошо. Но, как уже правильно отметили в комментариях, непереносимо, а на некоторых платформах и попросту небезопасно. Из лично известных мне — SPARC весьма щепетильно относится к выравниванию данных, и генерит исключение SIGBUS в случае доступа к неправильно выравненным данным.
Спасаться от лишнего паддинга можно и без pragma pack. Рецепт прост — поля в структуре «сортируются» по размеру. В начале помещаются самые большие (uint64, uint32), в конце — самые маленькие — (uint8). Все вопросы с порядком байт должны (на мой взгляд) решаться в точке сериализации и десериализации данных (ну, например, при отправке в сеть и получения из сети), а сам этот процесс, по хорошему, должен производиться не сразу в структуру, а путём последовательного чтения данных из буфера и размещения в соответствующих полях в специфичном для платформы виде. Может быть это не так «просто» и «изящно» выглядит, зато надёжно, переносимо и расширяемо.
Спасаться от лишнего паддинга можно и без pragma pack. Рецепт прост — поля в структуре «сортируются» по размеру. В начале помещаются самые большие (uint64, uint32), в конце — самые маленькие — (uint8). Все вопросы с порядком байт должны (на мой взгляд) решаться в точке сериализации и десериализации данных (ну, например, при отправке в сеть и получения из сети), а сам этот процесс, по хорошему, должен производиться не сразу в структуру, а путём последовательного чтения данных из буфера и размещения в соответствующих полях в специфичном для платформы виде. Может быть это не так «просто» и «изящно» выглядит, зато надёжно, переносимо и расширяемо.
+13
Когда Вы делаете #pragma pack(push, 1), Вы выигрываете несколько байтов и проигрываете несколько тактов процессора (процессор default архитектуры не станет работать с int32_t, чей адрес не выравнен на границу 4-х). Имхо, нет смысла этого делать, пока Вам не надо по какой-то причине ужать размер структуры. И даже если Вам таки надо ужать структуру, то, ИМХО, лучше руками перетасовать поля структуры, чем делать pragma pack.
+5
UFO just landed and posted this here
1. С помощью pragma pack Вы проигрываете такты процессора.
2. Без pragma pack тоже можно выделить нужное количество памяти и прочитать в нее структуру из файла, если приложение компилируется с одними и теми же настройками компилятора, одним и тем же компилятором. Если приложение компилируется в несколько либ, с разными настройками выравнивания, то чтения из файла это меньшая из Ваших проблем.
Использовать pragma pack, ИМХО, есть смысл использовать только в двух случаях:
1. Вы храните очень много данных в файле и размер жесткого диска и/или оперативной памяти сильно ограничены. В таком случае, скорее всего, процессорное время тоже очень дорого, по этому тут классический трейдоф между памятью и процессорным временем.
2. У Вас несколько версий программы, которые компилируется с разными настройками выравнивания. В таком случае, ИМХО, лучше использовать сериализацию, как писал FlexFerrum.
2. Без pragma pack тоже можно выделить нужное количество памяти и прочитать в нее структуру из файла, если приложение компилируется с одними и теми же настройками компилятора, одним и тем же компилятором. Если приложение компилируется в несколько либ, с разными настройками выравнивания, то чтения из файла это меньшая из Ваших проблем.
Использовать pragma pack, ИМХО, есть смысл использовать только в двух случаях:
1. Вы храните очень много данных в файле и размер жесткого диска и/или оперативной памяти сильно ограничены. В таком случае, скорее всего, процессорное время тоже очень дорого, по этому тут классический трейдоф между памятью и процессорным временем.
2. У Вас несколько версий программы, которые компилируется с разными настройками выравнивания. В таком случае, ИМХО, лучше использовать сериализацию, как писал FlexFerrum.
+2
Операция загрузки «пары мегабайт» как правило однократна. Ее производительность при этом маловажная вещь, ее можно не оптимизировать вообще.
А вот потом, при обработке этих данных, адресовать массив структур, который на 4 не делится будет более чем затратно.
имхо это плохое решение выиграть N милисекунд там где это неважно, чтобы потом проиграть N процентов там где это важно.
А вот потом, при обработке этих данных, адресовать массив структур, который на 4 не делится будет более чем затратно.
имхо это плохое решение выиграть N милисекунд там где это неважно, чтобы потом проиграть N процентов там где это важно.
+2
Ааааарррргх. Если процессор не может работать с невыровненными данными, значит компилятор, скорее всего, раскидает это на много команд чтения-сдвига-слияния.
И откуда вы (и многие другие комментаторы) взяли вообще, что программист волен раскидывать данные как хочет? Есть протокол обмена, есть заголовки, их хочется красиво разбирать (да и в код глянуть быстрее, чем искать стандарт и назначения полей). Я согласен, что стандарты обычно пишут красиво, но 24-битные числа тоже бывают.
И откуда вы (и многие другие комментаторы) взяли вообще, что программист волен раскидывать данные как хочет? Есть протокол обмена, есть заголовки, их хочется красиво разбирать (да и в код глянуть быстрее, чем искать стандарт и назначения полей). Я согласен, что стандарты обычно пишут красиво, но 24-битные числа тоже бывают.
-3
>>Когда Вы делаете #pragma pack(push, 1), Вы выигрываете несколько байтов и проигрываете несколько тактов процессора
>Ааааарррргх. Если процессор не может работать с невыровненными данными, значит компилятор, скорее всего, раскидает это на много команд чтения-сдвига-слияния.
Я про это и написал. О чем Вы пытаетесь спорить?
>И откуда вы (и многие другие комментаторы) взяли вообще, что программист волен раскидывать данные как хочет?
Это С++, детка, здесь могут и pragma pack написать, и delete this. Язык разрабатывался в расчете на то, что программист лучше знает. По этому, он дает возможность выстрелить себе в ногу. По этому, он дает возможность писать очень эффективный код для конкретного железа.
>Ааааарррргх. Если процессор не может работать с невыровненными данными, значит компилятор, скорее всего, раскидает это на много команд чтения-сдвига-слияния.
Я про это и написал. О чем Вы пытаетесь спорить?
>И откуда вы (и многие другие комментаторы) взяли вообще, что программист волен раскидывать данные как хочет?
Это С++, детка, здесь могут и pragma pack написать, и delete this. Язык разрабатывался в расчете на то, что программист лучше знает. По этому, он дает возможность выстрелить себе в ногу. По этому, он дает возможность писать очень эффективный код для конкретного железа.
+2
Нет, моё «Ааааарррргх ...» было к «процессор 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, т.к. так будет быстрее.
>Это С++, детка
Вот яркий пример: 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, т.к. так будет быстрее.
-3
Не надо путать протоколы обмена данными с представлением данных в памяти конкретной программы, работающей на конкретной архитектуре.
+2
Я перестал понимать, какую точку зрения Вы отстаиваете:
— pragma pack благо и его надо использовать всегда
— pragma pack вред и его нельзя использовать никогда
— pragma pack вред, но иногда его можно использовать
— pragma pack благо, но иногда лучше обойтись без него
— все любят котиков
— pragma pack благо и его надо использовать всегда
— pragma pack вред и его нельзя использовать никогда
— pragma pack вред, но иногда его можно использовать
— pragma pack благо, но иногда лучше обойтись без него
— все любят котиков
+2
А если не раскидает? Да и вообще, с какой стати компилятор должен раскидывать доступ к таким данным? Нет, может быть за последние 6 лет что-то и изменилось, но в середине 2000-ых сановские компиляторы таким точно не страдали.
Протокол обмена — это, обычно, хорошо стандартизированная и описанная штука. И данные, которые ходят по этому протоколу, совсем не обязательно должны «ложиться» на их бинарное представление в памяти. Более того, то, что они в данном конкретном случае ложатся — это большое везение. :) По этому при получении данных их таки лучше разобрать «ручками», и разложить по полям структуры, чем играть в рулетку и надеяться на то, что авось повезёт, и данные в память лягут так, как надо.
Протокол обмена — это, обычно, хорошо стандартизированная и описанная штука. И данные, которые ходят по этому протоколу, совсем не обязательно должны «ложиться» на их бинарное представление в памяти. Более того, то, что они в данном конкретном случае ложатся — это большое везение. :) По этому при получении данных их таки лучше разобрать «ручками», и разложить по полям структуры, чем играть в рулетку и надеяться на то, что авось повезёт, и данные в память лягут так, как надо.
+2
В моём случае повезло (вместе с первоначальным копированием из устройства), а универсальности не существует: всегда найдётся что-нибудь, что разрушит стройность. Вот я столкнулся с тем, что через PCI плохо записывать в 1 устройство меньше 4 байт (занулит остальное), а из другого читать больше 2 байт (тоже занулит). И да, в таком случае уже полагаться на компилятор не получается.
А вообще, ситуаций бесконечно много и в каждой свои методы удобнее. Можно лишь описать инструменты и *ожидаемую разработчиком* область применения.
А вообще, ситуаций бесконечно много и в каждой свои методы удобнее. Можно лишь описать инструменты и *ожидаемую разработчиком* область применения.
-2
Об том и речь. А по поводу «ожидаемой области применения»… У меня сложилось впечатление, что автор статьи сам слабо себе представляет цену тех советов, которые даёт. Такое впечатление сложилось вот почему. В первой части (про pragma pack) резюме такое: «Если вам мешает паддинг, выкиньте его с помощью прагмы!». И ни слова о том, зачем паддинг нужен, и почему от него надо избавляться только в самом крайнем случае. Из содержимого третьей части можно сделать вывод, что автор предлагает читать заголовок TCP/IP-пакета сразу в предложенную структуру. При этом нет ни слова о том, что, вообще говоря, структура эта не является POD-типом (потому что в ней присутствуют не-POD типы — те самые u16be), и такие манипуляции с ней не рекомендуются. Таким образом, в случае компиляции всего этого дела одним конкретным компилятором (Visual C++) может работать, а другом — нет.
+4
UFO just landed and posted this here
Насколько я помню, у битовых полей наблюдались проблемы с совместимостью, как между компиляторами, так и архитектурами.
Хотя было бы приятно узнать, что я неправ.
Хотя было бы приятно узнать, что я неправ.
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
У меня сразу простой вопрос, как с переносимостью кода на другие архитектуры, не x86? ARM (седьмой и девятый), MIPS например?
+2
за ARM9 и MIPS не скажу, но ARM7, емнип, только выравненные по 4-байтовой границе данные понимает. Cortex — побайтово
-1
Это больше к компилятору вопрос, умеет ли он нормально разруливать особенности архитектуры. GCC, например, глючил для SPARC, как уже выше писали (сейчас может исправился).
-1
Выше писали про SunCC (SunCC 5.5, если быть совсем точным) :)
0
если схемотехника камня физически не позволяет адресовать отдельные байты, никакие фишки компилятора тут не помогут
0
смотря что вы имеете в виду
реализовать обращение к невыровненному байтовому полю через, скажем, 32-битный read-modify-write компилятор может без особого труда, согласны?
реализовать обращение к невыровненному байтовому полю через, скажем, 32-битный read-modify-write компилятор может без особого труда, согласны?
0
Планирую в одной из следующих статей выложить идеальные, с моей точки зрения, структуры для работы с заголовками протоколов стека TCP/IP
Давай. А мы посмотрим, как оно заработает на PowerPC
+4
Для начало надо будет посмотреть, как оно заработает на x86 :) Т.к. описанная в статье структура IP пакета соответствует IP протоколу как раз на процессорах с big endian, а на LE будет отличаться.
0
У меня она работает на LittleEndian. Нижняя структура, там где
u16be
0
И какой же результат будет у этого кода?
Почему в последних 4х битах первого байта вдруг оказалась версия IP протокола, когда она должна быть в первых 4х?
IpHeader iphdr = {0};
iphdr.version = 4;
std::cou t<< std::hex << std::showbase << (int)*(uint8_t *)&iphdr << std::endl;
Почему в последних 4х битах первого байта вдруг оказалась версия IP протокола, когда она должна быть в первых 4х?
+2
Можно увидеть ваш вывод? И назовите компилятор и платформу, пожалуйста.
-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. В моем дистрибутиве в нем есть такие строчки:
Под другими ОС, которые поддерживают как LE так и BE платформы, и где такой файл в наличии, так же присутствует нечто подобное.
А так вывод 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 платформы, и где такой файл в наличии, так же присутствует нечто подобное.
+1
Спасибо, добавлю в статью что порядок выделения бит тоже зависит от порядка байт. Я действительно перепутал порядок. Надеюсь вскоре опубликовать статью с универсальными заголовками. Попытаюсь шаблонизировать битовые поля…
0
Хотелось бы услышать про магию u16be. Никогда не сталкивался.
+1
А чем не нравится htons(), ntohs(), htonl(), nthol(), да еще и на C++? Нужно изобрести свой велосипед по перетасовке бит?
+1
в свое время убил на это целый рабочий день — считывался поток данных по uart, а потом на считанный массив накладывался указатель на структуру и поидее сразу можно было таким образом удобно работать со считанными данными, но из за выравнивания после накладывания структуры — оказывался в полях какой то мусор — получалось — смотрю на структуру — вроде по размерам полей и порядку все сходится, смотрю на массив — вроде тоже, а вот в скомпилированном виде уже нифига не сходилось
0
Sign up to leave a comment.
Расставим точки над структурами C/C++