struct Node {
Node *left;
Node *right;
int height;
char data[];
};
É assim que eu costumava definir os nós da minha estrutura de dados. Acho muito útil porque posso incorporar os dados diretamente no nó; caso contrário, teria que usar um void*, o que implicaria em outro malloc e outra camada de indireção. No entanto, li que há dois problemas com esse tipo de código:
Alinhamento
Digamos que eu tenha uma função que copia alguns dados para o array de dados com memcpy. Se esses dados forem um "long" (longo), por exemplo, eles estariam desalinhados.
_Alignas(max_align_t) char data[];
consertar o problema?
Aliasing estrito
A questão é: contanto que eu acesse o array de dados com um ponteiro para um tipo que coincide com o tipo do objeto do qual copiei, a regra de aliasing estrito é obedecida, mesmo que eu teoricamente acesse um array de caracteres por meio de um ponteiro int, por exemplo? Este foi o único caso em que tive que lidar com código do tipo trocadilho.
Se você fizer ambas as coisas, ou seja, garantir que o membro da matriz flexível esteja alinhado ao máximo e acessar esse membro somente por meio de um ponteiro para o tipo do qual os dados foram copiados e alocar espaço dinamicamente, o comportamento será bem definido.
A Seção 6.5p6 do padrão C descreve o que acontece neste caso:
As seções em negrito são relevantes aqui. Veremos exatamente como isso se aplica no exemplo abaixo, supondo que o especificador de alinhamento seja aplicado ao
data
membro:Primeiro, o espaço é alocado dinamicamente para a
struct Node
mais 3 objetos do tamanho de along
e atribuídos anode
. Imediatamente após a alocação, os bytes alocados não têm tipo declarado, conforme a nota de rodapé 98.Após a chamada para
memcpy
, ossizeof(long)*3
bytes que começam emnode->data
são um objeto com um tipo efetivo delong[3]
conforme a seção em negrito, e o especificador de alinhamento nodata
membro garante que esse objeto esteja alinhado corretamente.A atribuição a
ptr
now resulta neste ponteiro apontando para o primeiro elemento dolong[3]
objeto que foi copiado viamemcpy
. O acesso subsequente a esses objetos por desreferenciaçãoptr
via sintaxe de indexação de array ocorre então por meio de um lvalue correspondente ao tipo dos objetos e, portanto, é válido.Além disso, o segundo loop, que acessou o array copiado diretamente por meio do
data
membro, também é válido, pois sempre permite o acesso a bytes de um objeto por meio de um tipo de caractere. Observe que isso se aplica apenas a leituras, pois as gravações alterariam o tipo efetivo do objeto.A Seção 6.5p7 especifica como um objeto pode ser acessado, com as duas seções em negrito sendo relevantes aqui:
Sim.
struct Node { Node *left; Node *right; int height; _Alignas(max_align_t) char data[]; };
permite que qualquer objeto de tipo fundamental seja armazenado com alinhamento correto emdata[]
.Considere também
size_t height
o dimensionamento e a indexação do array.int height
Pode ser muito pequeno.