Trouvez une raison à tout et vous comprendrez beaucoup.
Récemment, en regardant le code U-Boot dans la partie de l'implémentation SPI, je suis tombé sur une macro pour contourner la liste des périphériques disponibles, qui après plusieurs transitions m'a remis à la macro container_of. Le texte macro lui-même était présent, et avec une légère stupéfaction, j'ai vu qu'il était quelque peu différent de la version que j'avais précédemment vue. Une petite enquête a été menée, qui a conduit à des résultats intéressants.
La macro elle-même est connue depuis longtemps et résout un problème quelque peu étrange: nous avons un pointeur sur un champ d'une structure (ptr), nous connaissons le type de structure (type) et le nom du champ (membre), et nous devons obtenir un pointeur sur la structure dans son ensemble. Je ne comprends pas vraiment comment une telle tâche pourrait apparaître, peut-être que les auteurs "voulaient quelque chose d'étrange", mais une fois la tâche définie, elle doit être résolue. La solution est bien connue:
#define container_of(ptr, type, member) \ ((type *)((char *)(ptr)-offsetof(type,member)))
Tout est transparent et clair, mais l'implémentation découverte était un peu plus compliquée:
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
La deuxième ligne est presque identique à la première solution, mais que fait la première ligne de la macro? Non, ce qu'il fait est tout simplement compréhensible: il crée un pointeur local constant sur le champ de structure et lui attribue la valeur du paramètre macro - un pointeur sur le champ. Mais pourquoi cela est fait n'est pas clair.
Une considération évidente concernant le but de la première ligne est de vérifier le premier paramètre de la macro qu'il s'agit vraiment d'un pointeur vers un champ de structure dans le style:
int *x = (X)
qui dans ce cas prend une forme quelque peu abstruse:
typeof( ((type *)0)->member ) *__mptr = (ptr)
car le type requis pour la vérification doit être construit à la volée.
D'accord, soyons d'accord, la chose est utile, cependant, j'ai dû utiliser le pointeur résultant dans la deuxième ligne pour éviter les avertissements du compilateur.
Mais pourquoi le modificateur de constance - la macro devrait bien fonctionner même sans cet ajout (j'ai immédiatement essayé et cela a fonctionné). Les auteurs n'ont pas pu le dire par accident.
Il n'est pas nécessaire de regarderJe dois admettre qu'ils m'ont incité à la réponse, je n'ai pas deviné moi-même.
Il s'avère que ce modificateur est simplement nécessaire si nous essayons de trouver l'adresse de la structure constante, car dans ce cas, le compilateur aura besoin d'
invalid conversion from 'const xxx*' to `xxx*'
.
Il est intéressant de noter que la constance du champ lui-même à l'intérieur d'une structure ordinaire non seulement ne nécessite pas de contrainte dans une macro, mais supprime également son besoin d'une structure constante, c'est-à-dire une expression comme:
struct s1 { int data; const int next; }; const struct s1 ss1 = {314,0}; container_of(&(ss1.next),struct s1,next);
compile sans erreur et sans modificateur dans le texte de la macro, et l'expression:
struct s1 { int data; int next; }; const struct s1 ss1 = {314,0}; container_of(&(ss1.next),struct s1,next);
c'est obligatoire.
Pour les lecteurs de Habr qui connaissent bien le niveau de la langue, mes découvertes sembleront ridicules dans le style de «Merci, capitaine», mais pour le reste elles peuvent être intéressantes.