Найди всему причину и ты многое поймешь
Недавно, просматривая код U-Boot в части реализации SPI, наткнулся на макрос обхода списка доступных устройств, которые после нескольких переходов сбросил меня на макрос container_of. Сам текст макроса наличествовал и я с легким изумлением увидел, что он несколько отличается от ранее виденной мною версии. Было проведено небольшое расследование, которые привело к интересным результатам.
Сам по себе макрос известен давно и решает несколько странную задачу: у нас имеется указатель на поле некоторой структуры (ptr), нам известен тип структуры (type) и название поля (member) и необходимо получить указатель на структуру в целом. Я не очень понимаю, как такая задача могла появиться, возможно, авторам «хотелось странного», но, раз задача поставлена, ее надо решить. Решение общеизвестно:
#define container_of(ptr, type, member) ((type *)((char *)(ptr)-offsetof(type,member)))
Все прозрачно и понятно, но обнаруженная реализация была несколько сложнее:
#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );})
Вторая строка почти идентична первому решению, но что делает первая строка макроса? Нет, что она делает, как раз понятно: создает константный локальный указатель на поле структуры и присваивает ему значение параметра макроса — указателя на поле. А вот зачем это делается, неясно.
Очевидное соображение относительно назначения первой строки — проверка первого параметра макроса в том, что он действительно представляет собой указатель на поле структуры в стиле:
int *x = (X)
которая в данном случае принимает несколько заумный вид:
typeof( ((type *)0)->member ) *__mptr = (ptr)
поскольку требуемый для проверки тип приходится конструировать «на лету».
Ладно, согласимся, вещь полезная, правда, пришлось использовать полученный указатель во второй строке, чтобы избежать предупреждений компилятора.
Но зачем модификатор константности — макрос должен прекрасно работать и без этого дополнения (я тут же попробовал и получилось). Не могли же авторы поставить его случайно.
Туда смотреть не обязательно
Должен признаться, что мне подсказали ответ, я сам не догадался.
Оказывается, данный модификатор просто необходим, если мы попытаемся узнать адрес константной структуры, поскольку в этом случае от компилятора потребуется
invalid conversion from 'const xxx*' to `xxx*'
.Интересно, что константность самого поля внутри обычной структуры не только не требует приведения в макросе, но и снимает его необходимость при константной структуре, то есть выражение типа:
struct s1 {
int data;
const int next;
};
const struct s1 ss1 = {314,0};
container_of(&(ss1.next),struct s1,next);
компилируется без ошибок и без модификатора в тексте макроса, а выражение:
struct s1 {
int data;
int next;
};
const struct s1 ss1 = {314,0};
container_of(&(ss1.next),struct s1,next);
его обязательно требует.
Для тех читателей Хабра, кто хорошо знает стандарт языка, мои открытия покажутся смешными в стиле «Спасибо, капитан», но для остальных могут быть небезынтересны.
abondarev
ну почему же сразу странную. Это абсолютно нормальная задача для написания на Си, но с наследованием.
То есть мы по сути дела можем расширить какую то структуру своей приватной информацией. Зная указатель на информацию и что это точно поле в большей структуре, мы можем получить указатель на данную структуру.
GarryC Автор
Странность в том, что мы могли бы передать этот указатель в явном виде и это было бы правильнее, хотя я согласен, что возможны нюансы.