Qu'est-ce que les performances, la sécurité et l'alignement d'un membre de données sont cachés dans un tableau de caractères incorporé dans une classe C ++?

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

  •  05-07-2019
  •  | 
  •  

Question

J'ai récemment vu une base de code dont je crains qu'elle ne respecte les contraintes d'alignement. Je l'ai nettoyé pour produire un exemple minimal, donné ci-dessous. En bref, les joueurs sont:

  • Pool . C'est une classe qui alloue efficacement la mémoire, pour une définition du terme "efficace". Il est garanti que Pool renvoie un bloc de mémoire aligné pour la taille demandée.

  • Obj_list . Cette classe stocke des collections d'objets homogènes. Une fois que le nombre d'objets dépasse un certain seuil, il modifie sa représentation interne d'une liste à une arborescence. La taille de Obj_list est d'un pointeur (8 octets sur une plate-forme 64 bits). Son magasin peuplé dépassera bien sûr cette limite.

  • Agréger . Cette classe représente un objet très commun dans le système. Son histoire remonte au début de l'ère des stations de travail 32 bits, et il a été «optimisé» (à la même époque en 32 bits) pour utiliser le moins d'espace possible. Les agrégats peuvent être vides ou gérer un nombre arbitraire d'objets.

Dans cet exemple, les éléments Aggregate sont toujours alloués à partir de Pool , de sorte qu'ils sont toujours alignés. Les seules occurrences de Obj_list dans cet exemple sont les membres "masqués" des objets Aggregate . Par conséquent, ils sont toujours alloués à l'aide de placement new . Voici les classes de support:

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

Et voici Agrégat. Notez que la déclaration du membre 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)];
};

C'est ce membre de données qui m'inquiète le plus. Voici le pseudocode pour l’initialisation et l’accès:

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

Vous pourriez être tenté de suggérer de remplacer le tableau de caractères par un pointeur sur le type Obj_list , initialisé à NULL ou à une instance de la classe. Cela donne la bonne sémantique, mais change simplement le coût de la mémoire. Si la mémoire était encore insuffisante (c'est peut-être une représentation de la base de données EDA), remplacer le tableau de caractères par un pointeur sur une Obj_list coûterait un pointeur supplémentaire dans le cas où Les objets agrégés ont des membres.

En outre, je ne veux pas vraiment me laisser distraire de la question principale, qui est l’alignement. Je pense que la construction ci-dessus est problématique, mais je ne trouve pas vraiment plus dans la norme qu'une discussion vague sur le comportement d'alignement du 'système / bibliothèque' new .

Alors, la construction ci-dessus fait-elle autre chose que provoquer un décrochage occasionnel?

Modifier : je me rends compte qu'il existe des moyens de remplacer l'approche en utilisant le tableau de caractères incorporé. Tout comme les architectes d'origine. Ils les ont rejetés parce que la mémoire était rare. Maintenant, si j’ai une raison de toucher à ce code, je le changerai probablement.

Cependant, ma question concernant les problèmes d’alignement inhérents à cette approche est ce que j’espère que les gens aborderont. Merci!

Était-ce utile?

La solution

Ok, j'ai eu l'occasion de le lire correctement. Vous avez un problème d'alignement et invoquez un comportement non défini lorsque vous accédez au tableau de caractères en tant que liste_Obj. Il est fort probable que votre plate-forme fasse l’une des trois choses suivantes: vous permettre de vous en tirer, de vous en tirer à la peine d’une pénalité d’exécution ou de vous écraser de temps en temps avec une erreur de bus.

Vos options portables pour résoudre ce problème sont les suivantes:

  • allouer le stockage avec malloc ou une fonction d’allocation globale, mais vous pensez que c'est trop cher.
  • comme dit Arkadiy, faites de votre tampon un membre Obj_list:

    Obj_list list;
    

mais vous ne voulez plus payer le coût de la construction. Vous pouvez atténuer ce problème en fournissant un constructeur en ligne ne faisant rien à utiliser uniquement pour créer cette instance, comme le ferait le constructeur par défaut. Si vous suivez cette voie, envisagez fortement d’appeler le dtor

list.~Obj_list();

avant de faire un nouvel emplacement dans ce stockage.

Sinon, je pense qu'il ne vous reste plus que des options non portables: soit vous fiez à la tolérance de votre plate-forme vis-à-vis des accès mal alignés, soit vous utilisez les options non portables que votre compilateur vous a proposées.

Disclaimer: Il est tout à fait possible que je manque un tour aux syndicats ou à d’autres. C'est un problème inhabituel.

Autres conseils

L’alignement sera choisi par le compilateur en fonction de ses valeurs par défaut. Il s’agira probablement de quatre octets sous GCC / MSVC.

Cela ne devrait poser problème que s’il existe un code (SIMD / DMA) qui nécessite un alignement spécifique. Dans ce cas, vous devriez pouvoir utiliser les directives du compilateur pour vous assurer que member_list_store_d est aligné, ou augmenter la taille de (alignement-1) et utiliser un offset approprié.

Pouvez-vous simplement avoir une instance de Obj_list dans Aggregate? IOW, quelque chose dans le genre de

class Aggregate: public Lesser {    ... protégé:    Obj_list list; };

Je dois manquer quelque chose, mais je ne peux pas comprendre pourquoi c'est mauvais.

En ce qui concerne votre question, elle dépend parfaitement du compilateur. Cependant, la plupart des compilateurs alignent chaque membre sur la limite de mot, même si le type de membre n'a pas besoin d'être aligné de cette façon pour un accès correct.

Si vous souhaitez assurer l'alignement de vos structures, effectuez simplement une

.
// MSVC
#pragma pack(push,1)

// structure definitions

#pragma pack(pop)

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

Pour assurer l'alignement sur 1 octet de votre tableau de caractères dans Aggregate

Allouez le tableau de caractères member_list_store_d avec malloc ou l'opérateur global new [], l'un ou l'autre donnant un stockage aligné pour tout type.

Éditer: Il suffit de relire le PO - vous ne voulez pas payer pour un autre pointeur. Je vais relire dans la matinée.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top