40 votes

Les structures emballées sont-elles portables?

J'ai un code sur un Cortex-M4 microcontrôleur et souhaitez communiquer avec un PC à l'aide d'un protocole binaire. Actuellement, je suis en utilisant des paniers les structures à l'aide de la GCC spécifique packed d'attribut.

Voici une ébauche:

struct Sensor1Telemetry {
    int16_t temperature;
    uint32_t timestamp;
    uint16_t voltageMv;
    // etc...
} __attribute__((__packed__));

struct TelemetryPacket {
    Sensor1Telemetry tele1;
    Sensor2Telemetry tele2;
    // etc...
} __attribute__((__packed__));

Ma question est:

  • En supposant que j'utilise exactement la même définition pour l' TelemetryPacket struct sur le MCU et le client d'application, le code ci-dessus, être portable à travers de multiples plates-formes? (Je suis intéressé par les architectures x86 et x86_64, et le besoin qu'il fonctionne sur Windows, Linux et OS X.)
  • Faire d'autres compilateurs de soutien paniers structs avec la même disposition de la mémoire? Avec quelle syntaxe?

EDIT:

  • Oui, je sais emballé les structures sont non-standard, mais ils semblent utiles suffisante pour envisager de les utiliser.
  • Je m'intéresse à la fois le C et le C++, bien que je ne pense pas que GCC serait de les traiter différemment.
  • Ces structures ne sont pas héréditaires et ne pas hériter de quoi que ce soit.
  • Ces structures contiennent uniquement de taille fixe les champs de type entier, et d'autres semblables paniers des structures. (J'ai été brûlé par des flotteurs avant...)

27voto

geza Points 13730

Compte tenu de la mentionné plates-formes, oui, paniers structures sont complètement beaux à utiliser. x86 et x86_64 toujours soutenu les accès non alignés, et contrairement à la croyance commune, l'accès non alignés sur ces plates-formes a (presque) la même vitesse que alignés accès pendant un long moment (il n'y a pas une telle chose que non alignés, l'accès est beaucoup plus lent). Le seul inconvénient c'est que l'accès ne peut être atomique, mais je ne pense pas que c'est important dans ce cas. Et il y a un accord entre les compilateurs, paniers structs va utiliser la même mise en page.

GCC/clang prend en charge paniers structs avec la syntaxe que vous avez mentionné. MSVC a #pragma pack, qui peut être utilisée comme ceci:

#pragma pack(push, 1)
struct Sensor1Telemetry {
    int16_t temperature;
    uint32_t timestamp;
    uint16_t voltageMv;
    // etc...
};
#pragma pack(pop)

Deux questions peuvent se poser:

  1. Endianness doit être le même sur toutes les plateformes (le MICROCONTRÔLEUR doit être à l'aide de little-endian)
  2. Si vous affectez un pointeur vers un pique-struct membre, et vous êtes sur une architecture qui ne supporte pas les accès non alignés (ou des directives d'utilisation qui ont l'alignement des exigences, comme movaps ou ldrd), alors vous pouvez obtenir un plantage lors de l'utilisation pointeur (gcc ne pas vous avertir à ce sujet, mais clang n').

Voici la doc de GCC:

Les paniers attribut indique qu'une variable ou un champ de structure doit avoir le plus petit possible d'alignement-un octet pour une variable

Donc GCC garantit qu'aucun rembourrage sera utilisé.

MSVC:

Pour pack une classe est de placer ses membres directement les uns après les autres dans de mémoire

Donc MSVC garantit qu'aucun rembourrage sera utilisé.

Le seul "dangereux" de la zone, j'ai trouvé, est l'utilisation de bitfields. Ensuite, la disposition peut varier entre GCC et MSVC. Mais, il y a une option dans GCC, ce qui les rend compatibles: -mms-bitfields


Conseil: même si cette solution fonctionne maintenant, et il est très peu probable qu'il va arrêter de travailler, je vous recommande de garder la dépendance de votre code sur cette solution faible.

Note: j'ai seulement considéré comme GCC, clang et MSVC dans cette réponse. Il existe des compilateurs peut-être, pour qui ces choses ne sont pas vraies.

12voto

alk Points 26509

Si

  • L'endianité n'est pas un problème
  • les deux compilateurs gèrent correctement l'emballage
  • les définitions de type sur les deux implémentations C sont précises (conformes à la norme).

alors oui, les " structures compactes " sont portables.

À mon goût, trop de "si", ne faites pas ceci. Cela ne vaut pas la peine de se poser la question.

8voto

bazza Points 1828

Vous pourriez le faire, ou utiliser une solution alternative plus fiable.

Pour le noyau dur parmi les fanatiques de la sérialisation, il n'y est CapnProto. Cela vous donne un natif de la structure à traiter, et s'engage à s'assurer que lorsqu'elle est transférée à travers un réseau et légèrement travaillé, ça va encore faire sens à l'autre extrémité. Appeler à une sérialisation est presque inexacte; il vise à faire un petit peu que possible à la memmory représentation d'une structure. Peut-être prête pour le portage d'un M4

Il y a Google Protocol Buffers, c'est binaire. Plus bloaty, mais assez bon. Il y a l'accompagnement nanopb (plus adapté aux microcontrôleurs), mais ils ne font pas l'ensemble de la DGPG (je ne pense pas qu'il n' oneof). Beaucoup de gens l'utilisent avec succès cependant.

Certains de la C asn1 runtimes sont suffisamment petites pour une utilisation sur micro-contrôleurs. Je sais que ce celui qui s'adapte sur M0.

7voto

dwelch Points 27195

Vous ne devez jamais utiliser les structures à travers la compilation des domaines, à l'encontre de la mémoire (registres du matériel, de la cueillette à part les éléments de lire à partir d'un fichier ou d'un transfert de données entre les processeurs ou le même processeur de différents logiciels (entre une application et un pilote de noyau)). Vous êtes d'avoir des ennuis, car le compilateur a un peu libre de choisir l'alignement et de l'utilisateur sur le dessus de qui peut aggraver la situation en utilisant les modificateurs.

Non il n'y a aucune raison de supposer que vous pouvez le faire en toute sécurité à travers les plates-formes, même si vous utilisez le même compilateur gcc version par exemple contre des cibles différentes (différentes versions du compilateur ainsi que la cible de différences).

Pour réduire vos chances d'échec commencer par le plus grand des éléments de première (64 bits 32 bits 16 bits puis enfin tout 8 bits éléments) Idéalement aligner sur 32 minimum de peut-être 64 ans, qui on l'espère arm et x86, mais qui peut toujours changer ainsi que la valeur par défaut peut être modifié par toute personne qui construit le compilateur à partir des sources.

Maintenant, si c'est une sécurité de l'emploi chose, assurez-vous aller de l'avant, vous pouvez faire l'entretien régulier de ce code, probablement besoin d'une définition de chaque structure pour chaque cible (une copie du code source de la définition de la structure du BRAS et un autre pour les architectures x86, ou aura besoin de ce jour si pas immédiatement). Et puis tous ou tous les quelques versions de produits, vous serez appelé à effectuer des travaux sur le code...Bon peu d'entretien, des bombes à retardement qui vont...

Si vous voulez en toute sécurité à communiquer entre compiler des domaines ou des processeurs de la même ou de différentes architectures, utiliser un tableau de taille, un flux d'octets à un flux de halfwords ou un flot de paroles. Réduit de manière significative le risque de panne et de maintenance en bas de la route. Ne pas utiliser les structures de faire ressortir les éléments qui juste restaure le risque et l'échec.

La raison pourquoi les gens semblent penser que c'est bien à cause de l'utilisation de la même compilateur ou de la famille contre la même cible ou de la famille (ou les compilateurs provenant d'autres compilateurs choix), que vous comprenez les règles de la langue et où la mise en œuvre des zones définies sont éventuellement vous allez courir à travers une différence, il faut parfois des décennies dans votre carrière, il faut parfois des semaines...Ses "œuvres sur ma machine" problème...

2voto

Lorehead Points 953

Si vous voulez quelque chose de portable maximum, vous pouvez déclarer une mémoire tampon de uint8_t[TELEM1_SIZE] et memcpy() en dedans, en effectuant des conversions de finalité telles que htons() et htonl() (ou équivalents little-endian tels que ceux de la glib). Vous pouvez envelopper cela dans une classe avec des méthodes getter / setter en C ++ ou dans une structure avec des fonctions getter-setter en C.

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