30 votes

Comment interpréter une charge utile de message sans enfreindre les règles d'alias de type?

Mon programme reçoit des messages sur le réseau. Ces messages sont désérialisé par certains middleware (c'est à dire quelqu'un d'autre code que je ne peux pas changer). Mon programme reçoit des objets qui ressemblent à quelque chose comme ceci:

struct Message {
    int msg_type;
    std::vector<uint8_t> payload;
};

En examinant msg_type je peux déterminer que la charge utile du message est en fait, par exemple, un tableau de uint16_t valeurs. Je voudrais lire ce tableau sans une copie superflue.

Ma première pensée a été pour ce faire:

const uint16_t* a = reinterpret_cast<uint16_t*>(msg.payload.data());

Mais alors la lecture à partir d' a semble violer la norme. Voici la clause 3.10.10:

Si un programme tente d'accéder à la valeur d'un objet par l'intermédiaire d'un glvalue d'autre que l'un des types suivants le comportement est indéfini:

  • le type dynamique de l'objet,
  • un cv qualifiés version du type dynamique de l'objet,
  • un type similaire (tel que défini dans la section 4.4) pour le type dynamique de l'objet,
  • un type qui est signé ou non signé de type correspondant au type dynamique de l'objet,
  • un type qui est signé ou non signé de type correspondant à un cv qualifiés version du type dynamique de l'objet,
  • une agrégation ou une union de type qui comprend l'un des types mentionnés ci-dessus entre ses éléments, ou non membres de données (y compris, de manière récursive, un élément ou d'un non-membre de données statiques d'un subaggregate ou contenus de l'union),
  • un type qui est une (peut-être de cv qualifiés) de la classe de base de type de le type dynamique de l'objet,
  • un char ou unsigned char type.

Dans ce cas, a serait le glvalue et uint16_t* ne semble pas satisfait à aucun des critères énumérés.

Alors, comment dois-je traiter la charge utile, comme un tableau d' uint16_t valeurs sans avoir recours à un comportement indéfini ou de l'exécution d'une copie superflue?

16voto

Matt McNabb Points 14273

Si vous allez consommer les valeurs une par une, vous pouvez alors memcpy d'un uint16_t, ou écrire payload[0] + 0x100 * payload[1] etc. en ce qui concerne le comportement que vous voulez. Ce ne sera pas "inefficace".

Si vous appelez une fonction qui ne prend qu'un tableau d' uint16_t, et vous ne pouvez pas modifier la structure qui délivre Message, alors vous êtes hors de la chance. En C++ Standard, vous aurez à faire de la copie.

Si vous utilisez gcc ou clang, une autre option est de mettre en -fno-strict-aliasing lors de la compilation du code en question.

15voto

Daniel Langr Points 841

Si vous voulez strictement à suivre C++ Standard sans UB, et de ne pas employer des non-standard compilateur extensions, vous pouvez essayer:

uint16_t getMessageAt(const Message& msg, size_t i) {
   uint16_t tmp;
   memcpy(&tmp, msg.payload.data() + 2 * i, 2);
   return tmp;
}

Les optimisations du compilateur devrait éviter memcpy copie ici dans le code machine généré; voir, par exemple, le Type de beaucoup les jeux de mots, la Stricte Aliasing, et de l'Optimisation.

Il est, en fait, la copie dans la valeur de retour, mais en fonction de ce que vous allez faire avec elle, cette copie peut être optimisé à l'écart (par ex., cette valeur peut être chargé dans un registre et utilisé seulement là).

2voto

divinas Points 312

Si vous voulez être strictement exact, comme le standard que vous avez cité dit, vous ne pouvez pas. Si vous souhaitez un comportement bien défini, vous aurez besoin de faire une copie.

Si le code est conçu pour être portable, vous aurez besoin de gérer l'endianness de toute façon, et de reconstituer vos uint16_t valeurs de l'individu u_int8_t octets, et ce, par définition, exige une copie.

Si vous savez vraiment ce que vous faites, vous pouvez ignorer la norme, et il suffit de faire le reinterpret_cast que vous avez décrit.

GCC et clang soutien -fno-strict-aliasing pour prévenir l'optimisation de la génération de code cassé. Pour autant que je suis au courant, au moment de la rédaction de cet article le compilateur Visual Studio n'a pas de drapeau, et de ne jamais effectue ce genre d'optimisations - sauf si vous utilisez declspec(restrict) ou __restrict.

-3voto

Oliv Points 7148

Votre code ne peuvent pas être UB (ou de la ligne de frontière, en fonction de lecteur de sensibilité) si, par exemple, l' vector des données ont été construites de cette façon:

Message make_array_message(uint16_t* x, size_t n){
 Message m;
 m.type = types::uint16_t_array;
 m.payload.reserve(sizeof(uint16_t)*n);
 std::copy(x,x+n,reinterpret_cast<uint16_t*>(m.payload.data()));
 return m;
 }

Dans ce code, le vecteur de données d'organiser une séquence de uint16_t même si elle est déclarée en tant que uint8_t. Afin d'accéder aux données à l'aide de ce pointeur:

const uint16_t* a = reinterpret_cast<uint16_t*>(msg.payload.data());

Est parfaitement bien. Mais l'accès à l' vector's de données en tant que uint8_t serait UB. Accéder a[1] serait de travailler sur tous les compilateurs, mais c'est UB dans la norme actuelle. C'est sans doute un défaut dans la norme, et la normalisation de c++ comité de travail pour la fixer, voir P0593 la création d'objet Implicite pour le faible niveau de la manipulation d'objet.

Maintenant, dans mon propre code, je ne traite pas avec des défauts dans la norme, je préfère suivre comportement du compilateur parce que, pour ce sujet, c'est coder et compilateur de faire des règles et le standard n'aura qu'à suivre!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X