offsetof(s, m)
Если порыться по стандартным заголовочным фалам ANSI-C компилятора, то в файле stddef.h можно найти макрос offsetof()
Макрос offsetof() имеет тру-эмбеддерское объявление
Самое интересное, что несмотря на полезность этого макроса, он очень редко используется. Я и сам не знал о его существовании до сегодняшнего дня.
Надеюсь, из названия всем понятно, что делает этот макрос. Кому не понятно, объясняю. Он возвращает смещение поля в структуре. К примеру, есть структура:
Для того, чтобы понять, как offsetof() работает, рассмотрим по частям, как он разворачивается компилятором. Для примера, возьмем макрос от Кейла:
-
((s *)0) Приводит число ноль к указателю на структуру s. Эта строчка говорит компилятору, что по адресу 0 располагается структура, и мы получаем указатель на нее.
-
((s *)0)->m получает член m структуры s, компилятор будет думать, что этот член расположен по адресу 0 + смещение m
-
&(((s *)0)->m) вычисляем адрес члена m.
-
(size_t)&(((s *)0)->m) преобразовываем адрес члена m к целому числу.
Член m может быть любой сложности. К примеру, можно использовать вложенные структуры.
offsetof() так-же работает с union’ами
Теперь, кода вы понимаете, как работает этот макрос, попробуем привести пару реальных примеров его использования.
1. Чтение из энергонезависимой памяти
Для хранения настроек, многие микроконтроллерные системы содержат некую энергонезависимую
память, обычно это — EEPROM с последовательным интерфейсом. Обычно, драйвер EEPROM’а для чтения предоставляет функцию типа вот такой:
Естественно, возникает вопрос — какое смещение у запрашиваемой переменной от начала EEPROM. Типичное решение такой задачи — объявить структуру, которая повторяет содержимое EEPROM’а, объявить указатель на нее, и присвоить ему нулевой адрес
Как видно, выглядит довольно запутанно. Немного получше использовать offsetof()
Остается проблема с размером, который придется вводить в ручную. Для ее решения, можно по образу и подобию offsetof() написать макрос SIZE_OF_MEMBER():
теперь, чтение выглядит вот так
Виден повторяющийся кусочек "EEPROM, f", чтение можно еще упростить, объявив макрос
В итоге, чтение становится совсем простым:
Это то, что мы и хотели сказать компилятору "считай переменную f из EEPROM, и сохрани ее по адресу dest"
2. Защита энергонезависимой памяти
Много микроконтроллерных систем содержат память, содержимое которой может быть повреждено.
Пример такой памяти — ОЗУ с батарейным питанием в LPC2xxx или LPC1xxx.
Задача простая — нужно обнаружить — было ли повреждено содержимое такой памяти или нет. Для этого я добавляю в структуру, хранящуюся в такой памяти поле с CRC. Таким образом, обычно получается что-то вот такое:
CRC рассчитывается по всем полям структуры кроме самого себя. Если у нас есть функция, которая рассчитывает Crc последовательности байт, то кажется, достаточно сделать вот так:
Такой код будет работать только, если компилятор выравнивает данные по байтовой границе. Если данные будут выровнены по 4 байтовой границе (arm7, к примеру), то структура глазами компилятора будет выглядеть так:
pad- байты, которые не использует приложение. Они вставлены для организации выравнивания.
Таким образом, sizeof(nvram) = 16, а sizeof(nvram.crc) = 2. В результате, CRC будет рассчитываться с использованием своего старого значения. Упс.
Конечно-же, можно запаковать структуру плотнее с помощью #pragma pack, но когда это не желательно или невозможно, лучше использовать offsetof()
Сегодня, кстати, я наваял довольно огромную иерархию структур (ага, тоже для хранения в энергонезависимой памяти данных), где offsetof() встречается аж 3 раза. Штука получилась сложная, но без offsetof() все было бы в стопицот раз сложнее.
А где offsetof() используете вы? 🙂
Увидев в строке
#define offsetof(s, m) (size_t)&(((s *)0)->m)
жуткие «&» и «->», взгрустнул от своей необразованности и не «тру-эмбеддерости». Хорошо бы исправить.
Далее в тексте эта строка показана нормально.
Также, не все в курсе что такое size_t в Keil’e.
Грм… А в комменте эти «amp» и «gt» рисуются нормально…
Поправил. Нужно еще разобрваться с отображением openid ников. Даже и не представляю, где копать (
size_t — это не из кейла, а из стандартной библиотеки C. Википедия знает — https://ru.wikipedia.org/wiki/Stddef.h#.D0.A2.D0.B8.D0.BF_size_t
Упс. Пардоньте… Прикольно что Википедия про size_t знает, а IAR — нет. 🙂
IAR знает. Взгляните в файл %IAR_PATH%\avr\inc\clib\sysmac.h
А еще, взялните сюда — https://bsvi.me/rules/
Верю, ибо IAR у меня не установлен. Непонятно, чтож они тогда __INTADDR__ используют?
хз, скорее всего, __INTADDR__ каким-то особым образом обрабатывается компилятором в статике, и позволяет увеличить скорость компиляции. Врядле им стоит пользоваться извне библиотек иара.
>»Самое интересное, что несмотря на полезность этого макроса, он очень редко используется.»
Правила MISRA C не рекомендуют использовать offsetof.
Да мирса много чего не рекомендует. Ты попробуй пописать с соблюдением всех ее правил. Ты свихнешься сразу-же 🙂 Я пробовал. Ее нужно использовать только там, где отказ очень-очень критичен.
Ну, там не все обязательно. Довольно большая часть носит рекомендательный характер.
Кстати, я про этот стандарт узнал недавно, и по прочтении с удивлением обнаружил, что практически точно ему следую. O_O
очень странно, я, наоборот, очень сильно отклоняюсь. Попробовал писать в нем, оказалось жудко неудобно.
Ну, у меня тоже отклонения есть, но, как я понял, немного.
Даже интересно, а что Вас больше всего из правил раздражает (пару-тройку примеров, если несложно)? Интересно стили сравнить. 🙂
Я читал правила в доках к IAR’у.
Ссори, сейчас уже не помню. Помню просто, что траблов было много. Хотя, можно и привыкнуть. Я тоже использовал иар с включенной проверкой. Дак он ругался на все подряд 🙂