Pode haver muitas vantagens nesse código, mas infelizmente o Padrão C não foi escrito para facilitar. Historicamente, os compiladores ofereceram garantias comportamentais efetivas além do exigido pelo Padrão, que tornou possível escrever esse código de maneira muito mais limpa do que é possível no Padrão C, mas os compiladores começaram recentemente a revogar essas garantias em nome da otimização.
Mais notavelmente, muitos compiladores C garantem historicamente (por design, se não documentação) que, se dois tipos de estrutura contiverem a mesma sequência inicial, um ponteiro para qualquer tipo poderá ser usado para acessar membros dessa sequência comum, mesmo que os tipos não sejam relacionados, e ainda que, para fins de estabelecer uma sequência inicial comum, todos os indicadores de estruturas são equivalentes. O código que utiliza esse comportamento pode ser muito mais limpo e seguro para o tipo do que o código que não possui, mas, infelizmente, embora o Padrão exija que estruturas que compartilham uma sequência inicial comum sejam dispostas da mesma maneira, ele proíbe o código de realmente usar um ponteiro de um tipo para acessar a sequência inicial de outro.
Consequentemente, se você deseja escrever código orientado a objetos em C, terá que decidir (e deve tomar essa decisão desde o início) saltar por muitos obstáculos para cumprir as regras do tipo ponteiro de C e estar preparado para ter Os compiladores modernos geram código sem sentido, se alguém escorregar, mesmo que os compiladores mais antigos tenham gerado código que funcione conforme o planejado, ou documentem um requisito de que o código só possa ser usado com compiladores configurados para suportar o comportamento do ponteiro no estilo antigo (por exemplo, usando um "-fno-strict-aliasing") Algumas pessoas consideram "-fno-strict-aliasing" como mau, mas eu sugiro que seja mais útil pensar em "-fno-strict-aliasing" C como uma linguagem que oferece maior poder semântico para alguns propósitos do que C "padrão",mas à custa de otimizações que podem ser importantes para outros fins.
Por exemplo, em compiladores tradicionais, os compiladores históricos interpretariam o seguinte código:
struct pair { int i1,i2; };
struct trio { int i1,i2,i3; };
void hey(struct pair *p, struct trio *t)
{
p->i1++;
t->i1^=1;
p->i1--;
t->i1^=1;
}
executando as seguintes etapas na ordem: incremente o primeiro membro de *p
, complemente o bit mais baixo do primeiro membro de *t
, em seguida, diminua o primeiro membro de *p
e complemente o bit mais baixo do primeiro membro de *t
. Compiladores modernos reorganizarão a sequência de operações de uma maneira que codifique qual será mais eficiente se p
e t
identificar objetos diferentes, mas mudará o comportamento se não o fizerem.
É claro que este exemplo é deliberadamente artificial e, na prática, o código que usa um ponteiro de um tipo para acessar membros que fazem parte da sequência inicial comum de outro tipo geralmente funciona, mas infelizmente, já que não há como saber quando esse código pode falhar não é possível usá-lo com segurança, exceto desativando a análise de aliasing baseada em tipo.
Um exemplo um pouco menos artificial poderia ocorrer se alguém quisesse escrever uma função para fazer algo como trocar dois ponteiros por tipos arbitrários. Na grande maioria dos compiladores "C da década de 90", isso poderia ser realizado através de:
void swap_pointers(void **p1, void **p2)
{
void *temp = *p1;
*p1 = *p2;
*p2 = temp;
}
No padrão C, no entanto, seria necessário usar:
#include "string.h"
#include "stdlib.h"
void swap_pointers2(void **p1, void **p2)
{
void **temp = malloc(sizeof (void*));
memcpy(temp, p1, sizeof (void*));
memcpy(p1, p2, sizeof (void*));
memcpy(p2, temp, sizeof (void*));
free(temp);
}
Se *p2
for mantido no armazenamento alocado e o ponteiro temporário não for mantido no armazenamento alocado, o tipo efetivo de *p2
se tornará o tipo do ponteiro temporário e o código que tenta usar *p2
como qualquer tipo que não corresponda ao ponteiro temporário type invocará o comportamento indefinido. É extremamente improvável que um compilador note isso, mas como a filosofia moderna do compilador exige que os programadores evitem o comportamento indefinido a todo custo, não consigo pensar em outro meio seguro de escrever o código acima sem usar o armazenamento alocado .