2010-01-18 4 views
6

Abbiamo una struttura semplice (POD).Layout membro C++

struct xyz 
{ 
    float x, y, z; 
}; 

Posso supporre che il seguente codice sia OK? Posso supporre che non ci siano spazi? Cosa dice lo standard? È vero per i POD? È vero per le lezioni?

xyz v; 
float* p = &v.x; 
p[0] = 1.0f; 
p[1] = 2.0f; // Is it ok? 
p[2] = 3.0f; // Is it ok? 
+0

Penso che sì fintanto che tutti i membri 'float' sono uno accanto all'altro, ma non ho letto lo Standard :) – kennytm

+1

@KennyTM Quello che hai detto è vero, ma la parte importante è" ... come a lungo tutti i membri del float sono uno accanto all'altro ... ". Non c'è motivo di presumere che siano tutti vicini l'uno all'altro. –

+1

Poiché questa tecnica può essere controversa, un design migliore è avere una matrice all'interno di 'struct'. –

risposta

10

La risposta qui è un po 'complicata. Lo standard C++ afferma che i tipi di dati POD avranno garanzie di compatibilità del layout C (Reference). Secondo la sezione 9.2 della specifica C i membri di una struct saranno disposte in ordine sequenziale se

  1. Non c'è differenza accessibilità modificatore
  2. Nessun problema di allineamento con il tipo di dati

Così sì questa soluzione funzionerà finché il tipo float ha un allineamento compatibile sulla piattaforma corrente (è la dimensione della parola della piattaforma). Quindi questo dovrebbe funzionare per processori a 32 bit ma la mia ipotesi è che fallirebbe per quelli a 64 bit. Essenzialmente ovunque che sizeof(void*) è diverso da sizeof(float)

+1

Penso che potrebbe funzionare anche su macchine a 64 bit (anche se non hai la garanzia che lo standard dà): Se float si allarga a 64 bit tutto va bene (sizeof (float) == word size della piattaforma). Se il float rimane a 32 bit, potrebbe esserci una spaziatura di 32 bit aggiunta alla fine della struttura, ma i float potrebbero essere disposti in ordine sequenziale senza padding in mezzo. Ma questo è tutto indovinato e potrebbe esserci ancora qualche strana macchina a 64 bit usando il proprio 64 bit per ogni 32 bit float :-) – mmmmmmmm

+2

In pratica questo dovrebbe funzionare su qualsiasi piattaforma a cui possa pensare. Finché sizeof (float) == alignment_of (float), in sostanza. Ma non è garantito dallo standard, poiché l'allineamento è definito dall'implementazione. Non penso che sizeof (void *) abbia qualcosa a che fare con questo. – jalf

+0

@jalf, La mia comprensione dell'allineamento è limitata e tendo a pensare all'allineamento in termini di dimensioni dei puntatori quindi mi piacerebbe rimandare alla tua comprensione qui – JaredPar

4

No, non è OK farlo tranne per il primo campo.

Dalle norme C++:

9.2 I membri della classe
Un puntatore a un oggetto POD-struct, opportunamente convertito utilizzando un reinterpret_cast , punti alla sua membro iniziale (o se quel membro è un campo di bit , quindi all'unità in cui si trova e viceversa. [Nota: Potrebbe pertanto esserci un padding senza nome all'interno di un oggetto POD-struct, ma non all'inizio, come necessario per ottenere l'allineamento appropriato.

2

A seconda dell'hardware. Lo standard consente esplicitamente alle classi POD di avere padding non specificato e imprevedibile. Ho notato questo nella pagina di Wikipedia di C++ e ho afferrato la nota a piè di pagina con il riferimento alle specifiche per te.

^a b ISO/IEC (2003). ISO/IEC 14882: 2003 (E): Linguaggi di programmazione - C++ §9.2 Membri di classe [class.mem] para. 17

In termini pratici, tuttavia, su hardware e compilatori comuni andrà bene.

5

Questo non è garantito dallo standard e non funzionerà su molti sistemi. I motivi sono:

  • Il compilatore può allineare i membri della struttura come appropriato per la piattaforma di destinazione, che può significare allineamento a 32 bit, allineamento a 64 bit o qualsiasi altra cosa.
  • La dimensione del float può essere di 32 bit o 64 bit. Non c'è alcuna garanzia che sia uguale all'allineamento dei membri della struttura.

Ciò significa che p[1] potrebbe essere nella stessa posizione come xyz.y, o potrebbe sovrapporsi parzialmente, o non del tutto.

+0

Come ha detto JaredPar, questo non è vero.Se si hanno solo membri mobili e nessun identificatore di accesso, l'allineamento sarà lo stesso degli array. – MartinStettner

+1

Tuttavia, se si conoscono gli allineamenti e le dimensioni dei tipi incorporati, la disposizione dei membri della struttura POD senza modificatori di accesso è prevedibile. Ciò è importante per, ad esempio, la dichiarazione di strutture che descrivono interfacce hardware mappate in memoria. – moonshadow

+0

@MartinStettner: la risposta di JaredPar non contraddice questa risposta. Diventa un po 'più dettagliato quando è o non è probabile che funzioni. Ad esempio, è perfettamente possibile che un sistema desideri l'allineamento di 8 byte su flottanti a 4 byte, e lo standard di certo non lo proibisce. –

1

Lo standard richiede che l'ordine di disposizione in memoria corrisponda all'ordine di definizione, ma consente il riempimento arbitrario tra di loro. Se si dispone di un identificatore di accesso (public:, private: o protected:) tra i membri, anche la garanzia sull'ordine viene persa.

Modifica: nel caso specifico di tutti e tre i membri sono dello stesso tipo primitivo (cioè non sono essi stessi strutture o qualcosa di simile) si ha una buona probabilità - per i tipi primitivi, le dimensioni dell'oggetto e i requisiti di allineamento sono spesso lo stesso, quindi funziona.

OTOH, questo è solo per caso, e tende ad essere più debole di una forza; il codice è sbagliato, quindi idealmente fallirebbe immediatamente invece di apparire al lavoro, fino al giorno in cui darai una demo per il proprietario della società che diventerà il tuo cliente più importante, e in quel momento lo farà (naturalmente) fallire nella moda più atroce possibile ...

0

Il codice è OK (purché gestisca sempre e solo i dati generati nello stesso ambiente). La struttura verrà esposta in memoria come dichiarata se si tratta di POD. Tuttavia, in generale, c'è un trucco che devi sapere: il compilatore inserirà il padding nella struttura per garantire che i requisiti di allineamento di ciascun membro siano rispettati.

Aveva tuo esempio stati

struct xyz 
{ 
    float x; 
    bool y; 
    float z; 
}; 

allora z sarebbe iniziare 8 byte nella struttura e sizeof (xyz) sarebbe stato 12 come float s sono (di solito) 4 byte allineati.

Analogamente, nel caso

struct xyz 
{ 
    float x; 
    bool y; 
}; 

sizeof (xyz) == 8, per garantire ((xyz *) PTR) +1 restituisce un puntatore che obbedisce requisiti di allineamento di x.

Poiché i requisiti di allineamento/tipo di dimensioni possono variare tra compilatori/piattaforme, tale codice non è in genere portatile.

1

No, non si può presumere che non ci siano spazi vuoti. Puoi verificare la tua architettura, e se non ci sono e non ti interessa la portabilità, sarà OK.

Ma immagina un'architettura a 64 bit con float a 32 bit. Il compilatore può allineare i galleggianti della struct sui confini a 64 bit, e la vostra

p[1] 

vi darà spazzatura, e

p[2] 

ti darà quello che pensate che il vostro ottenere da

p[1] 

& c.

Tuttavia, il compilatore può fornire un modo per impacchettare la struttura.Non sarebbe ancora "standard" - lo standard non fornisce nulla di simile, e diversi compilatori forniscono modi molto incompatibili per farlo - ma è probabile che sia più portabile.

+1

BTW, è improbabile che il float sia a 64 bit su * qualsiasi * piattaforma: http://en.wikipedia.org/wiki/IEEE_754-2008 –

+1

Oh, e tu * dovresti * preoccuparti delle non-portabilità. Sono come pulci indistinte su un cane: non hanno ancora prurito, ma se non le pulite ora, lo faranno e saranno molto più difficili da eliminare quando lo faranno. –

+0

D'altra parte, se la piattaforma ha un'unità di elaborazione vettoriale, può allineare i float in modo tale che quattro si inseriscano in una singola regione da 16 byte. (Come nel caso di tutti i compilatori x86 che ho usato.) – greyfade

0

Come altri hanno sottolineato, l'allineamento non è garantito dalle specifiche. Molti dicono che dipende dall'hardware, ma in realtà dipende anche dal compilatore. L'hardware può supportare molti formati diversi. Ricordo che il compilatore PPC supporta i pragmi su come "impacchettare" i dati. Potresti impacchettarlo su confini "nativi" o forzarlo a 32 bit, ecc.

Sarebbe bello capire cosa stai cercando di fare. Se stai cercando di analizzare i dati di input, stai meglio con un parser reale. Se stai per serializzare, scrivi un serializzatore reale. Se stai provando a twittare bit come per un driver, allora le specifiche del dispositivo dovrebbero darti una mappa di memoria specifica su cui scrivere. Quindi puoi scrivere la struttura del tuo POD, specificare i pramenti di allineamento corretti (se supportati) e andare avanti.

2

In caso di dubbio, cambiare la struttura dei dati a seconda delle applicazioni:

struct xyz 
{ 
    float p[3]; 
}; 

Per facilitare la lettura si può prendere in considerazione:

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;} 
}; 

Forse anche aggiungere un po 'di incapsulamento:

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]; 
} 

In generale, se una struttura dati deve essere trattata in due o più modi diversi, forse il la struttura dei dati deve essere ridisegnata; o il codice.

1

consente di dare un'occhiata al codice sorgente di 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; 
} 

Funziona su molti sistemi.

-1

Presumo che tu voglia una struttura per mantenere le tue coordinate accessibili come membri (.x, .y e .z) ma vuoi comunque che siano accessibili, diciamo, un modo OpenGL (come se fosse un array) .

È possibile provare a implementare l'operatore [] della struttura in modo che sia possibile accedervi come una matrice. Qualcosa di simile:

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; 
    } 
    } 
}; 
0
  1. struttura di imballaggio (ad es #pragma pack in MSVC) http://msdn.microsoft.com/en-us/library/aa273913%28v=vs.60%29.aspx
  2. allineamento variabile (ad es __declspec(align( in MSVC) http://msdn.microsoft.com/en-us/library/83ythb65.aspx

sono due fattori che può rovinare le vostre ipotesi. i float sono generalmente di 4 byte di larghezza, quindi è raro disallineare variabili così grandi. Ma è ancora facile rompere il tuo codice.

Questo problema è più evidente quando la struttura dell'intestazione di una lettura binaria con cortocircuiti (come BMP o TGA) - dimenticando pack 1 causa un disastro.