смещение
С offsetof() Макрос — это функция библиотеки ANSI C, найденная в stddef.h . Он оценивает смещение (в байтах) данного члена внутри структуры или типа объединения , выражение типа размер_т . offsetof()
Макрос принимает два параметра : первый — это имя структуры или объединения, а второй — имя подобъекта структуры/объединения, который не является битовым полем . Его нельзя назвать прототипом C. [ 1 ]
Выполнение
[ редактировать ]«Традиционная» реализация макроса полагалась на то, что компилятор получал смещение члена путем указания гипотетической структуры, которая начинается с нулевого адреса:
#define offsetof(st, m) \
((size_t)&(((st *)0)->m))
Это можно понимать как получение нулевого указателя структуры типа. st , а затем получение адреса участника м внутри указанной структуры. Хотя эта реализация работает правильно во многих компиляторах, она вызвала некоторые споры относительно того, является ли это поведение неопределенным в соответствии со стандартом C. [ 2 ] поскольку это, по-видимому, предполагает разыменование нулевого указателя (хотя, согласно стандарту, раздел 6.6 Константные выражения, параграф 9, операция не обеспечивает доступ к значению объекта). Это также имеет тенденцию выдавать запутанную диагностику компилятора, если один из аргументов написан с ошибкой. [ нужна ссылка ]
Альтернатива:
#define offsetof(st, m) \
((size_t)((char *)&((st *)0)->m - (char *)0))
Это можно указать таким образом, поскольку стандарт не определяет, что внутреннее представление нулевого указателя находится по нулевому адресу. Поэтому необходимо определить разницу между адресом участника и базовым адресом.
Некоторые современные компиляторы (например, GCC ) вместо этого определяют макрос, используя специальную форму (как расширение языка), например [ 3 ]
#define offsetof(st, m) \
__builtin_offsetof(st, m)
Эта встроенная функция особенно полезна для классов C++, которые объявляют собственный унарный тип. оператор & . [ 4 ]
Использование
[ редактировать ]Это полезно при реализации общих структур данных на языке C. Например, ядро Linux использует offsetof() для реализации Container_of() , который позволяет чему-то вроде типа миксина найти структуру, которая его содержит: [ 5 ]
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
Этот макрос используется для извлечения охватывающей структуры из указателя на вложенный элемент, например, в этой итерации связанного списка. my_struct объекты :
struct my_struct {
const char *name;
struct list_node list;
};
extern struct list_node * list_next(struct list_node *);
struct list_node *current = /* ... */
while (current != NULL) {
struct my_struct *element = container_of(current, struct my_struct, list);
printf("%s\n", element->name);
current = list_next(&element->list);
}
РеализацияContainer_of в ядре Linux использует расширение GNU C, называемое выражениями операторов . [ 6 ] Возможно, выражение оператора использовалось для обеспечения безопасности типов и, следовательно, устранения потенциальных случайных ошибок. Однако существует способ реализовать то же поведение без использования выражений операторов, сохраняя при этом безопасность типов:
#define container_of(ptr, type, member) ((type *)((char *)(1 ? (ptr) : &((type *)0)->member) - offsetof(type, member)))
На первый взгляд эта реализация может показаться более сложной, чем необходимо, а необычное использование условного оператора может показаться неуместным. Возможна более простая реализация:
#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))
Эта реализация также будет служить той же цели, однако в исходной реализации ядра Linux есть фундаментальное упущение. Тип ptr никогда не сверяется с типом члена, это то, что может уловить реализация ядра Linux.
В вышеупомянутой реализации с проверкой типов проверка выполняется необычным использованием условного оператора. Ограничения условного оператора указывают, что если оба операнда условного оператора являются указателями на тип, они оба должны быть указателями на совместимые типы. В этом случае, несмотря на то, что значение третьего операнда условного выражения никогда не будет использовано, компилятор должен выполнить проверку, чтобы убедиться, что (ptr)
и &((type *)0)->member
оба являются совместимыми типами указателей.
Ограничения
[ редактировать ]Использование offsetof
ограничено типами POD в C++98 , классами стандартной компоновки в C++11 , [ 7 ] и больше случаев условно поддерживаются в C++17 , [ 8 ] в противном случае он имеет неопределенное поведение. Хотя большинство компиляторов выдают правильный результат даже в случаях, которые не соответствуют стандарту, существуют крайние случаи, когда offsetof либо выдаст неверное значение, либо выдаст предупреждение или ошибку во время компиляции, либо приведет к полному сбою программы. Особенно это касается виртуального наследования. [ 9 ]
Следующая программа выдаст несколько предупреждений и выведет явно подозрительные результаты при компиляции с gcc 4.7.3 на архитектуре amd64:
#include <stddef.h>
#include <stdio.h>
struct A
{
int a;
virtual void dummy() {}
};
struct B: public virtual A
{
int b;
};
int main()
{
printf("offsetof(A, a) : %zu\n", offsetof(A, a));
printf("offsetof(B, b) : %zu\n", offsetof(B, b));
return 0;
}
Выход:
offsetof(A, a) : 8 offsetof(B, b) : 8
Ссылки
[ редактировать ]- ^ «смещение ссылки» . MSDN . Проверено 19 сентября 2010 г.
- ^ «Вызывает ли &((имя структуры *)NULL -> b) неопределенное поведение в C11?» . Проверено 7 февраля 2015 г.
- ^ «Смещение ссылки GCC» . Фонд свободного программного обеспечения . Проверено 19 сентября 2010 г.
- ^ «Какова цель и тип возвращаемого значения оператора __builtin_offsetof?» . Проверено 20 октября 2012 г.
- ^ Грег Кроа-Хартман (июнь 2003 г.). "container_of()" . Linux-журнал . Проверено 19 сентября 2010 г.
- ^ «Утверждения и объявления в выражениях» . Фонд свободного программного обеспечения . Проверено 1 января 2016 г.
- ^ «смещение ссылки» . cplusplus.com . Проверено 1 апреля 2016 г.
- ^ «смещение ссылки» . cppreference.com . Проверено 20 июля 2020 г.
- ^ Стив Джессоп (июль 2009 г.). «Почему вы не можете использовать offsetof для структур, отличных от POD, в C++?» . Переполнение стека . Проверено 1 апреля 2016 г.