Encuentre una razón para todo y comprenderá mucho.
Recientemente, al mirar el código U-Boot en la parte de la implementación de SPI, me encontré con una macro para omitir la lista de dispositivos disponibles, que después de varias transiciones me restableció a la macro container_of. El texto macro en sí estaba presente, y con un ligero asombro vi que era algo diferente de la versión que había visto anteriormente. Se llevó a cabo una pequeña investigación que condujo a resultados interesantes.
La macro en sí misma se conoce desde hace mucho tiempo y resuelve un problema un tanto extraño: tenemos un puntero a un campo de alguna estructura (ptr), conocemos el tipo de estructura (tipo) y el nombre del campo (miembro), y necesitamos obtener un puntero a la estructura como un todo. Realmente no entiendo cómo podría aparecer una tarea así, tal vez los autores "querían algo extraño", pero una vez que la tarea está establecida, debe resolverse. La solución es bien conocida:
#define container_of(ptr, type, member) \ ((type *)((char *)(ptr)-offsetof(type,member)))
Todo es transparente y claro, pero la implementación descubierta fue algo más complicada:
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
La segunda línea es casi idéntica a la primera solución, pero ¿qué hace la primera línea de la macro? No, lo que hace es comprensible: crea un puntero local constante al campo de estructura y le asigna el valor del parámetro macro, un puntero al campo. Pero por qué esto se hace no está claro.
Una consideración obvia con respecto al propósito de la primera línea es verificar el primer parámetro de la macro que realmente es un puntero a un campo de estructura en el estilo:
int *x = (X)
que en este caso toma una forma algo abstrusa:
typeof( ((type *)0)->member ) *__mptr = (ptr)
ya que el tipo requerido para la verificación debe construirse sobre la marcha.
De acuerdo, estemos de acuerdo, la cosa es útil, sin embargo, tuve que usar el puntero resultante en la segunda línea para evitar advertencias del compilador.
Pero, ¿por qué el modificador de constancia? La macro debería funcionar bien incluso sin esta adición (lo intenté de inmediato y funcionó). Los autores no pudieron decirlo por accidente.
No es necesario mirar allíDebo admitir que me dieron la respuesta, yo mismo no lo adiviné.
Resulta que este modificador es simplemente necesario si intentamos encontrar la dirección de la estructura constante, porque en este caso el compilador necesitará una
invalid conversion from 'const xxx*' to `xxx*'
.
Es interesante que la constancia del campo dentro de una estructura ordinaria no solo no requiera coerción en una macro, sino que también elimina la necesidad de una estructura constante, es decir, una expresión como:
struct s1 { int data; const int next; }; const struct s1 ss1 = {314,0}; container_of(&(ss1.next),struct s1,next);
compila sin errores y sin un modificador en el texto macro, y la expresión:
struct s1 { int data; int next; }; const struct s1 ss1 = {314,0}; container_of(&(ss1.next),struct s1,next);
Se requiere.
Para aquellos lectores de Habr que conocen bien el estándar del idioma, mis descubrimientos parecerán ridículos al estilo de "Gracias, capitán", pero por lo demás pueden ser interesantes.