Membro C++ layout
Pergunta
Vamos a ter uma estrutura simples (POD).
struct xyz
{
float x, y, z;
};
Talvez eu suponha que o código a seguir é OK?Talvez eu suponha que não há qualquer lacunas?O que a norma diz?É verdade para a Casca?É verdade para as aulas?
xyz v;
float* p = &v.x;
p[0] = 1.0f;
p[1] = 2.0f; // Is it ok?
p[2] = 3.0f; // Is it ok?
Solução
A resposta aqui é um pouco complicada. O padrão C ++ diz que os tipos de dados de pod terão garantias de compatibilidade de layout C (Referência). De acordo com a seção 9.2 da especificação C, os membros de uma estrutura serão dispostos em ordem seqüencial se
- Não há diferença de modificador de acessibilidade
- Nenhum problema de alinhamento com o tipo de dados
Então, sim, esta solução funcionará enquanto o tipo float
Possui um alinhamento compatível na plataforma atual (é o tamanho da palavra da plataforma). Portanto, isso deve funcionar para processadores de 32 bits, mas meu palpite é que ele falharia por 64 bits. Essencialmente em qualquer lugar disso sizeof(void*)
é diferente de sizeof(float)
Outras dicas
Isso não é garantido pelo padrão e não funcionará em muitos sistemas. As razões são:
- O compilador pode alinhar os membros da estrutura conforme apropriado para a plataforma de destino, o que pode significar alinhamento de 32 bits, alinhamento de 64 bits ou qualquer outra coisa.
- O tamanho do flutuador pode ser de 32 bits ou 64 bits. Não há garantia de que seja o mesmo que o alinhamento do membro da estrutura.
Isso significa que p[1]
pode estar no mesmo local que xyz.y
, ou pode se sobrepor parcialmente, ou não.
Não, não é bom fazê -lo, exceto pelo primeiro campo.
Dos padrões C ++:
9.2 Membros da classe
Um ponteiro para um objeto de estrutura de pod, adequadamente convertido usando um reinterpret_cast, aponta para seu membro inicial (ou se esse membro for um campo de bits, então para a unidade em que reside) e vice-versa. [Nota: portanto, pode haver preenchimento sem nome em um objeto de estrutura de pod, mas não no seu início, conforme necessário para obter o alinhamento apropriado.
Depende do hardware. O padrão permite explicitamente que as classes de POD tenham estofamento não especificado e imprevisível. Eu notei isso na página do C ++ Wikipedia e peguei a nota de rodapé com a referência de especificação para você.
^ AB ISO/IEC (2003). ISO/IEC 14882: 2003 (e): linguagens de programação - C ++ §9.2 Membros da classe [classe.mem] par. 17
Em termos práticos, no entanto, em hardware e compiladores comuns, ficará bem.
Em caso de dúvida, altere a estrutura de dados para se adequar ao aplicativo:
struct xyz
{
float p[3];
};
Para uma legibilidade, você pode considerar:
struct xyz
{
enum { x_index = 0, y_index, z_index, MAX_FLOATS};
float p[MAX_FLOATS];
float X(void) const {return p[x_index];}
float X(const float& new_x) {p[x_index] = new_x;}
float Y(void) const {return p[y_index];}
float Y(const float& new_y) {p[y_index] = new_y;}
float Z(void) const {return p[z_index];}
float Z(const float& new_z) {p[z_index] = new_z;}
};
Talvez até adicione mais um pouco de encapsulamento:
struct Functor
{
virtual void operator()(const float& f) = 0;
};
struct xyz
{
void for_each(Functor& ftor)
{
ftor(p[0]);
ftor(p[1]);
ftor(p[2]);
return;
}
private:
float p[3];
}
Em geral, se uma estrutura de dados precisar ser tratada de duas ou mais maneiras diferentes, talvez a estrutura de dados precise ser redesenhada; ou o código.
O padrão exige que a ordem de arranjo na memória corresponda à ordem da definição, mas permita o preenchimento arbitrário entre eles. Se você tem um especificador de acesso (public:
, private:
ou protected:
) Entre os membros, mesmo a garantia sobre a ordem é perdida.
Editar: No caso específico de todos os três membros do mesmo tipo primitivo (ou seja, de estruturas ou algo assim), você tem uma chance bastante justa - para tipos primitivos, os requisitos de tamanho e alinhamento do objeto são frequentemente os mesmos, então Funciona.
Otoh, isso é apenas por acidente e tende a ser mais uma fraqueza do que uma força; O código está errado, então, idealmente, falharia imediatamente em vez de parecer trabalhar, até o dia em que você está dando uma demonstração para o proprietário da empresa que será seu cliente mais importante, quando será ( Claro) falhar da maneira mais hedionda possível ...
Não, você pode não assumir que não há lacunas. Você pode verificar sua arquitetura e, se não houver e não se importa com a portabilidade, ficará bem.
Mas imagine uma arquitetura de 64 bits com carros alegóricos de 32 bits. O compilador pode alinhar os carros alegóricos da estrutura em limites de 64 bits, e seu
p[1]
vai te dar lixo e
p[2]
darei a você o que você acha que você recebe de
p[1]
& c.
No entanto, seu compilador pode lhe dar uma maneira de embalar a estrutura. Ainda não seria "padrão"-o padrão não fornece tal coisa, e diferentes compiladores fornecem maneiras muito incompatíveis de fazer isso-mas é provável que seja mais portátil.
Vamos dar uma olhada no código -fonte Doom III:
class idVec4 {
public:
float x;
float y;
float z;
float w;
...
const float * ToFloatPtr( void ) const;
float * ToFloatPtr( void );
...
}
ID_INLINE const float *idVec4::ToFloatPtr( void ) const {
return &x;
}
ID_INLINE float *idVec4::ToFloatPtr( void ) {
return &x;
}
Funciona em muitos sistemas.
Seu código está ok (desde que ele apenas lida com dados gerados no mesmo ambiente). A estrutura será apresentada na memória, conforme declarado se for o pod. No entanto, em geral, há um Gotcha que você precisa estar ciente: o compilador inserirá o preenchimento na estrutura para garantir que os requisitos de alinhamento de cada membro sejam obedecidos.
Seu exemplo foi
struct xyz
{
float x;
bool y;
float z;
};
Então Z teria começado 8 bytes na estrutura e tamanho de (xyz) teria sido 12 como float
S são (geralmente) 4 bytes alinhados.
Da mesma forma, no caso
struct xyz
{
float x;
bool y;
};
sizeof (xyz) == 8, para garantir ((xyz*) ptr) +1 retorna um ponteiro que obedece aos requisitos de alinhamento de X.
Como os requisitos de alinhamento / tamanhos de tipo podem variar entre compiladores / plataformas, esse código não é em geral portátil.
Como outros apontaram, o alinhamento não é garantido pelas especificações. Muitos dizem que depende do hardware, mas na verdade também depende do compilador. O hardware pode suportar muitos formatos diferentes. Lembro -me de que o compilador PPC suporta Pragmas para "embalar" os dados. Você pode embalá -lo nos limites 'nativos' ou forçá -lo a limites de 32 bits, etc.
Seria bom entender o que você está tentando fazer. Se você está tentando 'analisar' os dados de entrada, é melhor com um analisador real. Se você estiver indo para serializar, escreva um serializador real. Se você estiver tentando girar bits, como um driver, a especificação do dispositivo deve fornecer um mapa de memória específico para gravar. Em seguida, você pode escrever sua estrutura de pod, especificar os pragmas de alinhamento correto (se suportados) e seguir em frente.
- estrutura de embalagem (por exemplo,
#pragma pack
no MSVC) http://msdn.microsoft.com/en-us/library/aa273913%28v=vs.60%29.aspx - variável de alinhamento
(por exemplo,
__declspec(align(
no MSVC) http://msdn.microsoft.com/en-us/library/83ythb65.aspx
são dois fatores que podem destruir seus pressupostos.carros alegóricos são geralmente 4 bytes de largura, é raro desalinhar tão grande de variáveis.Mas é ainda fácil de quebrar seu código.
Este problema é mais visível quando o binário de leitura de cabeçalho estrutura com shorts (como BMP ou TGA) - esquecer pack 1
causa um desastre.
Suponho que você queira uma estrutura para manter suas coordenadas acessadas como membros (.x, .y e .z), mas você ainda deseja que elas sejam acessadas, digamos, uma maneira do OpenGL (como se fosse uma matriz).
Você pode tentar implementar o operador [] da estrutura para que ele possa ser acessado como uma matriz. Algo como:
struct xyz
{
float x, y, z;
float& operator[] (unsigned int i)
{
switch (i)
{
case 0:
return x;
break;
case 1:
return y;
break;
case 2:
return z;
break;
default:
throw std::exception
break;
}
}
};