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?
Foi útil?

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

  1. Não há diferença de modificador de acessibilidade
  2. 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 floatS 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.

  1. estrutura de embalagem (por exemplo, #pragma pack no MSVC) http://msdn.microsoft.com/en-us/library/aa273913%28v=vs.60%29.aspx
  2. 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;
    }
  }
};
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top