Estou ciente de que existem outras questões semelhantes. Eu li isso e não acho que essa pergunta tenha sido respondida.
Posso mover-me entre uma sequência consecutiva (contígua na sequência de declaração) de campos do mesmo tipo de uma estrutura usando aritmética de ponteiro sem consultar _Alignof
?
Posso mover-me entre os elementos da matriz sem consultar _Alignof
porque o preenchimento final está incluído.
No entanto, é possível que campos consecutivos do mesmo tipo em uma estrutura não estejam alinhados da mesma forma que em uma matriz?
Em outras palavras, é possível que esse código tenha um comportamento indefinido?
struct MyStruct {
long int field_1;
int field_2;
int field_3;
};
int main(void) {
struct MyStruct my_struct;
int *field_2_ptr = &my_struct.field_2;
int field_3_value = *(field_2_ptr + 1);
}
Eu sei que isso é uma má prática. Estou ciente . _ ->
Eu sei que o preenchimento pode interferir em geral. Quero saber se o preenchimento pode interferir nesta circunstância específica.
Esta questão é sobre C. Não me importo com C++.
Até agora eu compilei isso com o GCC e experimentei o Valgrind para ver se algo estava errado, e compilei com clang e UBSan. Parece estar bem neste sistema (x86-64 Linux).
A rigor, este é um comportamento indefinido.
A aritmética de ponteiro é permitida em objetos de matriz (e objetos únicos são tratados como uma matriz de 1 elemento para essa finalidade), desde que o ponteiro original e o ponteiro resultante apontem para o mesmo objeto de matriz (ou um elemento após o final). Além disso, um ponteiro para um após o fim não pode ser desreferenciado, caso contrário, acionará um comportamento indefinido.
Isso está explicado na seção 6.5.6p8 do padrão C em relação aos operadores aditivos:
A seção em negrito é exatamente o que está acontecendo aqui:
Como isso desreferencia um ponteiro para outro elemento de (essencialmente) uma matriz de 1 elemento, isso desencadeia um comportamento indefinido. Isso
&my_struct.field_2 + 1 == &my_struct.field_3
seria avaliado como verdadeiro, não importa.Pela mesma razão, este também é um comportamento indefinido:
Embora o procedimento acima provavelmente funcione, não há garantia disso. Os compiladores modernos otimizam agressivamente e podem e assumem que não existe comportamento indefinido e exploram essa suposição.
Já vi casos em que, dados ponteiros
a
eb
que apontam para a memória adjacente,(uintptr_t)(a+1) == (uintptr_t)b
são verdadeiros, masa+1 == b
são falsos.Como @dbush escreveu, é um UB.
Eu usaria um array para agrupar membros do mesmo tipo:
e seu código não invocará um UB
Esse tipo de comportamento foi definido na linguagem que o Padrão C foi designado para descrever, e o Padrão C permite que implementações, como uma forma de "extensão de linguagem conforme", suportem tais construções. Clang e gcc suportarão tais construções quando invocados com o
-fno-strict-alising
sinalizador do compilador, embora não se deva esperar que eles suportem tais construções de outra forma.