Найди всему причину и ты многое поймешь


Недавно, просматривая код 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);

его обязательно требует.

Для тех читателей Хабра, кто хорошо знает стандарт языка, мои открытия покажутся смешными в стиле «Спасибо, капитан», но для остальных могут быть небезынтересны.

Комментарии (2)


  1. abondarev
    10.09.2019 17:05

    Сам по себе макрос известен давно и решает несколько странную задачу

    ну почему же сразу странную. Это абсолютно нормальная задача для написания на Си, но с наследованием.
    То есть мы по сути дела можем расширить какую то структуру своей приватной информацией. Зная указатель на информацию и что это точно поле в большей структуре, мы можем получить указатель на данную структуру.


    1. GarryC Автор
      10.09.2019 17:34

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