Обычно используемым макросом в ядре linux (и других местах) является container_of
, который (в основном) определяется следующим образом:
#define container_of(ptr, type, member) (((type) *)((char *)(ptr) - offsetof((type), (member))))
Что в принципе позволяет восстановить "родительскую" структуру с указателем на один из его членов:
struct foo {
char ch;
int bar;
};
...
struct foo f = ...
int *ptr = &f.bar; // 'ptr' points to the 'bar' member of 'struct foo' inside 'f'
struct foo *g = container_of(ptr, struct foo, bar);
// now, 'g' should point to 'f', i.e. 'g == &f'
Однако не совсем ясно, считается ли вычитание, содержащееся в container_of
undefined.
С одной стороны, поскольку bar
внутри struct foo
- это только одно целое число, то только *ptr
должен быть действительным (а также ptr + 1
). Таким образом, container_of
эффективно создает выражение, подобное ptr - sizeof(int)
, что является undefined (даже без разыменования).
С другой стороны, в §6.3.2.3 с .7 из стандартных состояний С, которые преобразуют указатель на другой тип и обратно, должен указывать один и тот же указатель. Поэтому, "перемещая" указатель на середину объекта struct foo
, а затем назад к началу должен выводить исходный указатель.
Основная проблема заключается в том, что реализациям разрешено проверять индексацию вне пределов времени во время выполнения. Моя интерпретация этого и вышеупомянутого требования эквивалентности указателя заключается в том, что границы должны быть сохранены во всех полях указателей (это включает в себя распад указателя - иначе, как вы могли бы использовать указатель на итерацию по массиву?). Ergo, а ptr
может быть только указателем int
, и ни ptr - 1
, ни *(ptr + 1)
не действительны, ptr
должно все еще иметь некоторое представление о том, что находится в середине структуры, так что (char *)ptr - offsetof(struct foo, bar)
(даже если на практике указатель равен ptr - 1
).
Наконец, я натолкнулся на то, что если у вас есть что-то вроде:
int arr[5][5] = ...
int *p = &arr[0][0] + 5;
int *q = &arr[1][0];
в то время как поведение undefined для разыменования p
, сам указатель действителен и должен сравниваться с q
(см. этот вопрос), Это означает, что p
и q
сравнивают одно и то же, но могут быть разными в определенном порядке реализации (так что только q
можно разыменовать). Это может означать следующее:
// assume same 'struct foo' and 'f' declarations
char *p = (char *)&f.bar;
char *q = (char *)&f + offsetof(struct foo, bar);
p
и q
сравнивают одно и то же, но могут иметь разные границы, связанные с ними, поскольку приведения к (char *)
происходят от указателей к несовместимым типам.
Чтобы подвести итог, стандарт C не совсем ясен в отношении такого типа поведения, и попытка применить другие части стандарта (или, по крайней мере, мои интерпретации их) приводит к конфликтам. Итак, можно ли строго определить container_of
? Если да, то правильное ли определение выше?
Это обсуждалось здесь после комментариев на мой ответ на этот вопрос.