Encontre um motivo para tudo e você entenderá muito.
Recentemente, observando o código U-Boot na parte da implementação do SPI, deparei-me com uma macro para ignorar a lista de dispositivos disponíveis, que após várias transições me redefinem para a macro container_of. O próprio texto macro estava presente e, com um leve espanto, vi que era um pouco diferente da versão que eu havia visto anteriormente. Uma pequena investigação foi realizada, o que levou a resultados interessantes.
A macro em si é conhecida há muito tempo e resolve um problema um tanto estranho: temos um ponteiro para um campo de alguma estrutura (ptr), sabemos o tipo de estrutura (tipo) e o nome do campo (membro), e precisamos obter um ponteiro para a estrutura como um todo. Eu realmente não entendo como essa tarefa poderia aparecer, talvez os autores "quisessem algo estranho", mas depois que a tarefa é definida, ela precisa ser resolvida. A solução é bem conhecida:
#define container_of(ptr, type, member) \ ((type *)((char *)(ptr)-offsetof(type,member)))
Tudo é transparente e claro, mas a implementação descoberta foi um pouco mais complicada:
#define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );})
A segunda linha é quase idêntica à primeira solução, mas o que a primeira linha da macro faz? Não, o que ele faz é apenas compreensível: ele cria um ponteiro local constante para o campo de estrutura e atribui a ele o valor do parâmetro macro - um ponteiro para o campo. Mas por que isso é feito não está claro.
Uma consideração óbvia em relação ao objetivo da primeira linha é verificar o primeiro parâmetro da macro que realmente é um ponteiro para um campo de estrutura no estilo:
int *x = (X)
que neste caso assume uma forma um tanto obscura:
typeof( ((type *)0)->member ) *__mptr = (ptr)
já que o tipo necessário para verificação deve ser construído em tempo real.
Ok, vamos concordar, a coisa é útil, no entanto, eu tive que usar o ponteiro resultante na segunda linha para evitar avisos do compilador.
Mas por que o modificador de constância - a macro deve funcionar bem mesmo sem essa adição (tentei imediatamente e funcionou). Os autores não puderam colocá-lo por acidente.
Não é necessário olhar para láDevo admitir que eles me deram a resposta, eu mesmo não adivinhei.
Acontece que esse modificador é simplesmente necessário se tentarmos descobrir o endereço da estrutura constante, pois nesse caso o compilador precisará de uma
invalid conversion from 'const xxx*' to `xxx*'
.
É interessante que a constância do próprio campo dentro de uma estrutura comum não exija apenas coerção em uma macro, mas também elimine a necessidade de uma estrutura constante, ou seja, uma expressão como:
struct s1 { int data; const int next; }; const struct s1 ss1 = {314,0}; container_of(&(ss1.next),struct s1,next);
compila sem erros e sem um modificador no texto da macro e a expressão:
struct s1 { int data; int next; }; const struct s1 ss1 = {314,0}; container_of(&(ss1.next),struct s1,next);
é necessário.
Para aqueles leitores de Habr que conhecem bem o padrão da língua, minhas descobertas parecerão ridículas no estilo de "Obrigado, capitão", mas, pelo resto, podem ser interessantes.