offsetof(s, m)

Опубликовано в рубрике "Статьи", 01.09.2010.
Тэги: , , , автор:

Если порыться по стандартным заголовочным фалам ANSI-C компилятора, то в файле stddef.h можно найти макрос offsetof()

Макрос offsetof() имеет тру-эмбеддерское объявление

1// у кейла
2#define offsetof(s, m) (size_t)&(((s *)0)-›m)
3
4// у IAR’а
5#define offsetof(T, m) (__INTADDR__((&((T *)0)-›m)))

Самое интересное, что несмотря на полезность этого макроса, он очень редко используется. Я и сам не знал о его существовании до сегодняшнего дня.

Надеюсь, из названия всем понятно, что делает этот макрос. Кому не понятно, объясняю. Он возвращает смещение поля в структуре. К примеру, есть структура:

1#pragma pack(push, 1)
2struct SomeStruct
3{
4 uint8 a;
5 uint16 b;
6 uint8 c;
7}
8
9// offsetof(SomeStruct, a) вернет 0,
10// offsetof(SomeStruct, b) вернет 1
11// offsetof(SomeStruct, c) вернет 3

Для того, чтобы понять, как offsetof() работает, рассмотрим по частям, как он разворачивается компилятором.  Для примера, возьмем макрос от Кейла:

1#define offsetof(s, m) (size_t)&(((s *)0)-›m)

  • ((s *)0) Приводит число ноль к указателю на структуру s. Эта строчка говорит компилятору, что  по адресу 0 располагается структура, и мы получаем указатель на нее.
  • ((s *)0)->m получает член m структуры s, компилятор будет думать, что этот член расположен по адресу 0 + смещение m
  • &(((s *)0)->m) вычисляем адрес члена m.
  • (size_t)&(((s *)0)->m) преобразовываем адрес члена m к целому числу.

 

Член m может быть любой сложности. К примеру, можно использовать вложенные структуры.

1struct AnotherStruct
2{
3 uint8 m;
4 uint8 n;
5};
6
7struct SomeStruct
8{
9 uint8 a;
10 uint16 b;
11 uint8 c;
12 AnotherStruct another_struct;
13}
14
15offsetof(SomeStruct, another_struct.m);

 

offsetof() так-же работает с union’ами

Теперь, кода вы понимаете, как работает этот макрос, попробуем привести пару реальных примеров его использования.

 

1. Чтение из энергонезависимой памяти

Для хранения настроек, многие микроконтроллерные системы содержат некую энергонезависимую
память, обычно это — EEPROM с последовательным интерфейсом.  Обычно, драйвер EEPROM’а для чтения предоставляет функцию типа вот такой:

1EepromRead(uint32 offset, uint32 count, uint8 *dest);

Естественно, возникает вопрос — какое смещение у запрашиваемой переменной от начала EEPROM. Типичное решение такой задачи — объявить структуру, которая повторяет содержимое EEPROM’а, объявить указатель на нее, и присвоить ему нулевой адрес

1struct Eeprom
2{
3 uint32 i;
4 float f;
5 uint8 c;
6};
7
8const Eeprom *const p_eeprom = 0x0000000;
9EepromRead(&(p_eeprom-›f), sizeof(p_eeprom-›f), dest);

Как видно, выглядит довольно запутанно. Немного получше использовать offsetof()

1struct Eeprom
2{
3 uint32 i;
4 float f;
5 uint8 c;
6};
7
8EepromRead(offsetof(Eeprom, f), 4, dest);

Остается проблема с размером, который придется вводить в ручную. Для ее решения, можно по образу и подобию offsetof() написать макрос SIZE_OF_MEMBER():

1#define SIZE_OF_MEMBER(s,m) ((size_t) sizeof(((s *)0)-›m));

теперь, чтение выглядит вот так

1EepromRead(offsetof(Eeprom, f), SIZE_OF_MEMBER(Eeprom, f), &dest);

Виден повторяющийся кусочек "EEPROM, f", чтение можно еще упростить, объявив макрос

1#define EEPROM_READ(M,D) EepromRead(offsetof(Eeprom, M), SIZE_OF_MEMBER(Eeprom, M), D);

В итоге, чтение становится совсем простым:

1EEPROM_READ(f, &dest);

Это то, что мы и хотели сказать компилятору "считай переменную f из EEPROM, и сохрани ее по адресу dest"

 

2. Защита энергонезависимой памяти

Много микроконтроллерных систем содержат память, содержимое которой может быть повреждено.
Пример такой памяти — ОЗУ с батарейным питанием в LPC2xxx или LPC1xxx.

Задача простая — нужно обнаружить — было ли повреждено содержимое такой памяти или нет. Для этого я добавляю в структуру, хранящуюся в такой памяти поле с CRC. Таким образом, обычно получается что-то вот такое:

1struct Nvm
2{
3 uint32 i;
4 uint8 f;
5 uint8 c;
6 uint16 crc;
7};
8
9Nvm nvram;

 

CRC рассчитывается по всем полям структуры кроме самого себя. Если у нас есть функция, которая рассчитывает Crc последовательности байт, то кажется, достаточно сделать вот так:

1nvram.crc = crc16((char *)&nvram, sizeof(nvram)-sizeof(nvram.crc));

Такой код будет работать только, если компилятор выравнивает данные по байтовой границе. Если данные будут выровнены по 4 байтовой границе (arm7, к примеру), то структура глазами  компилятора будет выглядеть так:

1struct Nvm
2{
3 uint32 i; — смещение 0
4 uint8 f; — смещение 4
5 uint8 pad1[3];
6 uint8 c; — смещение 8
7 uint8 pad2[3];
8 uint16 crc; — смещение 12
9 uint8 pad[2];
10};

 

pad- байты, которые не использует приложение. Они вставлены для организации выравнивания.

Таким образом, sizeof(nvram) = 16, а sizeof(nvram.crc) = 2. В результате, CRC будет рассчитываться с использованием своего старого значения.  Упс.

Конечно-же, можно запаковать структуру плотнее с помощью #pragma pack, но когда это не желательно или невозможно, лучше использовать offsetof()

1nvram.crc = crc16((uint8_t *) &nvram, offsetof(Nvm, crc));

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

А где offsetof() используете вы? 🙂




Комментарии
  1. google.com/profiles/gm… написал(а) 19th Октябрь, 2010 в 12:48

    Увидев в строке
    #define offsetof(s, m) (size_t)&(((s *)0)->m)
    жуткие «&» и «->», взгрустнул от своей необразованности и не «тру-эмбеддерости». Хорошо бы исправить.
    Далее в тексте эта строка показана нормально.
    Также, не все в курсе что такое size_t в Keil’e.

  2. google.com/profiles/gm… написал(а) 19th Октябрь, 2010 в 12:53

    Грм… А в комменте эти «amp» и «gt» рисуются нормально…

  3. BSVi написал(а) 19th Октябрь, 2010 в 13:03

    Поправил. Нужно еще разобрваться с отображением openid ников. Даже и не представляю, где копать (

    size_t — это не из кейла, а из стандартной библиотеки C. Википедия знает — https://ru.wikipedia.org/wiki/Stddef.h#.D0.A2.D0.B8.D0.BF_size_t

  4. google.com/profiles/gm… написал(а) 19th Октябрь, 2010 в 13:10

    Упс. Пардоньте… Прикольно что Википедия про size_t знает, а IAR — нет. 🙂

  5. BSVi написал(а) 19th Октябрь, 2010 в 13:24

    IAR знает. Взгляните в файл %IAR_PATH%\avr\inc\clib\sysmac.h
    А еще, взялните сюда — https://bsvi.me/rules/

  6. google.com/profiles/gm… написал(а) 19th Октябрь, 2010 в 13:38

    Верю, ибо IAR у меня не установлен. Непонятно, чтож они тогда __INTADDR__ используют?

  7. BSVi написал(а) 19th Октябрь, 2010 в 13:44

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

  8. YS написал(а) 16th Март, 2012 в 18:47

    >»Самое интересное, что несмотря на полезность этого макроса, он очень редко используется.»

    Правила MISRA C не рекомендуют использовать offsetof.

  9. BSVi написал(а) 19th Март, 2012 в 13:36

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

  10. YS написал(а) 19th Март, 2012 в 13:53

    Ну, там не все обязательно. Довольно большая часть носит рекомендательный характер.

    Кстати, я про этот стандарт узнал недавно, и по прочтении с удивлением обнаружил, что практически точно ему следую. O_O

  11. BSVi написал(а) 19th Март, 2012 в 13:59

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

  12. YS написал(а) 19th Март, 2012 в 14:02

    Ну, у меня тоже отклонения есть, но, как я понял, немного.

    Даже интересно, а что Вас больше всего из правил раздражает (пару-тройку примеров, если несложно)? Интересно стили сравнить. 🙂

    Я читал правила в доках к IAR’у.

  13. BSVi написал(а) 21st Март, 2012 в 16:09

    Ссори, сейчас уже не помню. Помню просто, что траблов было много. Хотя, можно и привыкнуть. Я тоже использовал иар с включенной проверкой. Дак он ругался на все подряд 🙂

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *


Срок проверки reCAPTCHA истек. Перезагрузите страницу.