Quali sono le prestazioni, la sicurezza e l'allineamento di un membro Data nascosto in un array di caratteri incorporato in una classe C ++?

StackOverflow https://stackoverflow.com/questions/247639

  •  05-07-2019
  •  | 
  •  

Domanda

Recentemente ho visto una base di codice che temo stia violando i vincoli di allineamento. L'ho lavato per produrre un esempio minimo, dato di seguito. In breve, i giocatori sono:

  • Pool . Questa è una classe che alloca la memoria in modo efficiente, per alcune definizioni di "efficiente". Pool è garantito per restituire un blocco di memoria allineato per la dimensione richiesta.

  • Obj_list . Questa classe memorizza raccolte omogenee di oggetti. Una volta che il numero di oggetti supera una determinata soglia, cambia la sua rappresentazione interna da un elenco a un albero. La dimensione di Obj_list è un puntatore (8 byte su una piattaforma a 64 bit). Il suo negozio popolato ovviamente supererà quello.

  • Aggregate . Questa classe rappresenta un oggetto molto comune nel sistema. La sua storia risale ai primi tempi delle workstation a 32 bit ed è stato "ottimizzato" (nella stessa era a 32 bit) per utilizzare il minor spazio possibile. Aggregati possono essere vuoti o gestire un numero arbitrario di oggetti.

In questo esempio, gli elementi aggregati sono sempre allocati da Pool , quindi sono sempre allineati. Le uniche occorrenze di Obj_list in questo esempio sono i membri "nascosti" negli oggetti Aggregati , e quindi sono sempre allocati usando posizionamento nuovo . Ecco le classi di supporto:

class Pool
{
public:
   Pool();
   virtual ~Pool();
   void *allocate(size_t size);
   static Pool *default_pool();   // returns a global pool
};

class Obj_list
{
public:
   inline void *operator new(size_t s, void * p) { return p; }

   Obj_list(const Args *args);
   // when constructed, Obj_list will allocate representation_p, which
   // can take up much more space.

   ~Obj_list();

private:
   Obj_list_store *representation_p;
};

Ed ecco Aggregate. Nota che la dichiarazione del membro member_list_store_d :

// Aggregate is derived from Lesser, which is twelve bytes in size
class Aggregate : public Lesser
{
public:
   inline void *operator new(size_t s) {
      return Pool::default_pool->allocate(s);
   }

   inline void *operator new(size_t s, Pool *h) {
      return h->allocate(s);
   }

public:

   Aggregate(const Args *args = NULL);
   virtual ~Aggregate() {};

   inline const Obj_list *member_list_store_p() const;

protected:
   char member_list_store_d[sizeof(Obj_list)];
};

È quel membro di dati che mi preoccupa di più. Ecco lo pseudocodice per l'inizializzazione e l'accesso:

Aggregate::Aggregate(const Args *args)
{
   if (args) {
      new (static_cast<void *>(member_list_store_d)) Obj_list(args);
   }
   else {
      zero_out(member_list_store_d);
   }
}

inline const Obj_list *Aggregate::member_list_store_p() const
{
   return initialized(member_list_store_d) ? (Obj_list *) &member_list_store_d : 0;
}

Potresti essere tentato di suggerire di sostituire l'array char con un puntatore al tipo Obj_list , inizializzato su NULL o su un'istanza della classe. Ciò fornisce la semantica corretta, ma sposta semplicemente il costo della memoria. Se la memoria fosse ancora ad un premio (e potrebbe essere, questa è una rappresentazione del database EDA), la sostituzione dell'array char con un puntatore a un Elenco_oggetti costerebbe un altro puntatore nel caso in cui Gli oggetti aggregati hanno hanno membri.

Oltre a ciò, non voglio davvero distrarmi dalla domanda principale qui, che è l'allineamento. penso che il costrutto di cui sopra sia problematico, ma non riesco davvero a trovare di più nello standard di qualche vaga discussione sul comportamento di allineamento del "sistema / libreria" nuovo .

Quindi, il costrutto sopra fa qualcosa di più che causare una stalla occasionale?

Modifica : mi rendo conto che ci sono modi per sostituire l'approccio usando l'array char incorporato. Così fecero gli architetti originali. Li hanno scartati perché la memoria era un premio. Ora, se ho un motivo per toccare quel codice, probabilmente lo cambierò.

Tuttavia, la mia domanda, sulle questioni di allineamento insite in questo approccio, è ciò che spero che le persone affronteranno. Grazie!

È stato utile?

Soluzione

Ok - ho avuto la possibilità di leggerlo correttamente. Si riscontra un problema di allineamento e si richiama un comportamento indefinito quando si accede all'array char come Obj_list. Molto probabilmente la tua piattaforma farà una delle tre cose: lasciarti andare via, lasciarti andare con una penalità di runtime o occasionalmente andare in crash con un errore del bus.

Le opzioni portatili per risolvere questo problema sono:

  • alloca la memoria con malloc o una funzione di allocazione globale, ma pensi che sia anche questo costoso.
  • come dice Arkadiy, fai del tuo buffer un membro Obj_list:

    Obj_list list;
    

ma ora non vuoi pagare i costi di costruzione. È possibile mitigarlo fornendo un costruttore do-nothing inline da utilizzare solo per creare questa istanza, come pubblicato dal costruttore predefinito. Se segui questa strada, considera fortemente di invocare il dtor

list.~Obj_list();

prima di inserire un posizionamento in questo spazio di archiviazione.

Altrimenti, penso che ti rimarranno opzioni non portatili: o fai affidamento sulla tolleranza della tua piattaforma per gli accessi non allineati, oppure usa qualsiasi opzione non portabile che il tuo compilatore ti offre.

Disclaimer: è del tutto possibile che mi manchi un trucco con i sindacati o qualcosa del genere. È un problema insolito.

Altri suggerimenti

L'allineamento verrà scelto dal compilatore in base ai suoi valori predefiniti, questo probabilmente finirà come quattro byte in GCC / MSVC.

Questo dovrebbe essere un problema solo se esiste un codice (SIMD / DMA) che richiede un allineamento specifico. In questo caso dovresti essere in grado di utilizzare le direttive del compilatore per assicurarti che member_list_store_d sia allineato o aumenti le dimensioni di (allineamento-1) e usi un offset appropriato.

Puoi semplicemente avere un'istanza di Obj_list all'interno di Aggregate? IOW, qualcosa del genere

classe Aggregate: Lesser pubblico {    ... protetta:    Elenco obj_list; };

Devo mancare qualcosa, ma non riesco a capire perché questo è negativo.

Quanto alla tua domanda, dipende perfettamente dal compilatore. La maggior parte dei compilatori, tuttavia, allineerà ogni membro al limite delle parole per impostazione predefinita, anche se il tipo di membro non deve essere allineato in questo modo per un accesso corretto.

Se vuoi assicurare l'allineamento delle tue strutture, fai semplicemente

// MSVC
#pragma pack(push,1)

// structure definitions

#pragma pack(pop)

// *nix
struct YourStruct
{
    ....
} __attribute__((packed));

Per garantire l'allineamento di 1 byte dell'array di caratteri in aggregato

Assegna l'array di caratteri member_list_store_d con malloc o l'operatore globale new [], uno dei quali darà spazio di archiviazione allineato per qualsiasi tipo.

Modifica: basta leggere nuovamente l'OP - non si desidera pagare per un altro puntatore. Leggerà di nuovo al mattino.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top