Le problème
Dans un contexte embarqué bare-metal de bas niveau , je voudrais créer un espace vide dans la mémoire, dans une structure C ++ et sans aucun nom, pour interdire à l'utilisateur d'accéder à un tel emplacement mémoire.
Pour le moment, je l'ai réalisé en mettant un vilain uint32_t :96;
champ de bits qui remplacera commodément trois mots, mais cela soulèvera un avertissement de GCC (Bitfield trop grand pour tenir dans uint32_t), ce qui est assez légitime.
Bien que cela fonctionne bien, ce n'est pas très propre lorsque vous souhaitez distribuer une bibliothèque avec plusieurs centaines de ces avertissements ...
Comment faire ça correctement?
Pourquoi y a-t-il un problème en premier lieu?
Le projet sur lequel je travaille consiste à définir la structure mémoire des différents périphériques de toute une ligne de microcontrôleurs (STMicroelectronics STM32). Pour ce faire, le résultat est une classe qui contient une union de plusieurs structures qui définissent tous les registres, en fonction du microcontrôleur ciblé.
Voici un exemple simple de périphérique assez simple: une entrée / sortie à usage général (GPIO)
union
{
struct
{
GPIO_MAP0_MODER;
GPIO_MAP0_OTYPER;
GPIO_MAP0_OSPEEDR;
GPIO_MAP0_PUPDR;
GPIO_MAP0_IDR;
GPIO_MAP0_ODR;
GPIO_MAP0_BSRR;
GPIO_MAP0_LCKR;
GPIO_MAP0_AFR;
GPIO_MAP0_BRR;
GPIO_MAP0_ASCR;
};
struct
{
GPIO_MAP1_CRL;
GPIO_MAP1_CRH;
GPIO_MAP1_IDR;
GPIO_MAP1_ODR;
GPIO_MAP1_BSRR;
GPIO_MAP1_BRR;
GPIO_MAP1_LCKR;
uint32_t :32;
GPIO_MAP1_AFRL;
GPIO_MAP1_AFRH;
uint32_t :64;
};
struct
{
uint32_t :192;
GPIO_MAP2_BSRRL;
GPIO_MAP2_BSRRH;
uint32_t :160;
};
};
Où tout GPIO_MAPx_YYY
est une macro, définie soit comme uint32_t :32
soit le type de registre (une structure dédiée).
Ici, vous voyez uint32_t :192;
qui fonctionne bien, mais cela déclenche un avertissement.
Ce que j'ai considéré jusqu'à présent:
J'aurais pu le remplacer par plusieurs uint32_t :32;
(6 ici), mais j'ai quelques cas extrêmes où j'ai uint32_t :1344;
(42) (entre autres). Je préférerais donc ne pas ajouter une centaine de lignes sur 8k autres, même si la génération de structure est scriptée.
Le message d'avertissement exact est quelque chose comme:
width of 'sool::ll::GPIO::<anonymous union>::<anonymous struct>::<anonymous>' exceeds its type
(J'adore à quel point c'est louche).
Je préfère ne pas résoudre ce problème en supprimant simplement l'avertissement, mais en utilisant
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-WTheRightFlag"
/* My code */
#pragma GCC diagnostic pop
peut être une solution ... si je trouve TheRightFlag
. Cependant, comme indiqué dans ce fil , gcc/cp/class.c
avec cette triste partie de code:
warning_at (DECL_SOURCE_LOCATION (field), 0,
"width of %qD exceeds its type", field);
Ce qui nous indique qu'il n'y a pas de -Wxxx
drapeau pour supprimer cet avertissement ...
la source
char unused[12];
et ainsi de suite?uint32_t :192;
.:42*32
place de:1344
Réponses:
Utilisez plusieurs champs de bits anonymes adjacents. Donc au lieu de:
par exemple, vous auriez:
Un pour chaque registre dont vous voulez être anonyme.
Si vous avez de grands espaces à remplir, il peut être plus clair et moins sujet aux erreurs d'utiliser des macros pour répéter l'espace unique de 32 bits. Par exemple, étant donné:
Ensuite, un espace de 1344 (42 * 32 bits) peut être ajouté ainsi:
la source
uint32_t :1344;
est à la place) donc je préférerais ne pas avoir à suivre cette voie ...Que diriez-vous d'une manière C ++ - ish?
Vous obtenez une saisie semi-automatique à cause de l'
GPIO
espace de noms et il n'y a pas besoin de remplissage factice. Même, ce qui se passe est plus clair, car vous pouvez voir l'adresse de chaque registre, vous n'avez pas du tout à vous fier au comportement de remplissage du compilateur.la source
ldr r0, [r4, #16]
, tandis que les compilateurs sont plus susceptibles de manquer cette optimisation avec chaque adresse déclarée séparément. GCC chargera probablement chaque adresse GPIO dans un registre séparé. (À partir d'un pool littéral, bien que certains d'entre eux puissent être représentés comme des éléments immédiats pivotés dans l'encodage Thumb.)static volatile uint32_t &MAP0_MODER
, noninline
. Uneinline
variable ne se compile pas. (static
évite d'avoir un stockage statique pour le pointeur, etvolatile
c'est exactement ce que vous voulez pour MMIO pour éviter l'élimination de la mémoire morte ou l'optimisation de l'écriture / lecture.)static
fait tout aussi bien pour ce cas. Merci d'avoir mentionnévolatile
, je vais l'ajouter à ma réponse (et changer en ligne en statique, donc cela fonctionne pour le pré C ++ 17).GPIOA::togglePin()
.Dans l'arène des systèmes embarqués, vous pouvez modéliser le matériel en utilisant une structure ou en définissant des pointeurs vers les adresses de registre.
La modélisation par structure n'est pas recommandée car le compilateur est autorisé à ajouter un remplissage entre les membres à des fins d'alignement (bien que de nombreux compilateurs pour systèmes embarqués aient un pragma pour empaqueter la structure).
Exemple:
Vous pouvez également utiliser la notation de tableau:
Si vous devez utiliser la structure, à mon humble avis, la meilleure méthode pour ignorer les adresses serait de définir un membre et de ne pas y accéder:
Dans l'un de nos projets, nous avons à la fois des constantes et des structures de différents fournisseurs (le fournisseur 1 utilise des constantes tandis que le fournisseur 2 utilise des structures).
la source
static
membres d'une structure en supposant que la saisie semi-automatique est capable d'afficher les membres statiques. Sinon, il peut également s'agir de fonctions membres en ligne.public:
etprivate:
autant de fois que vous le souhaitez, pour obtenir le bon ordre des champs.)public
et lesprivate
membres de données non statiques, il n'est pas un type de mise en page standard , donc il ne fournit pas les garanties de commande que vous êtes la pensée de. (Et je suis presque sûr que le cas d'utilisation de l'OP nécessite un type de mise en page standard.)volatile
ces déclarations, BTW, pour les registres d'E / S mappés en mémoire.geza a raison de ne pas vouloir utiliser de classes pour cela.
Mais, si vous insistez, la meilleure façon d'ajouter un membre inutilisé d'une largeur de n octets est simplement de le faire:
Si vous ajoutez un pragma spécifique à l'implémentation pour empêcher l'ajout d'un remplissage arbitraire aux membres de la classe, cela peut fonctionner.
Pour GNU C / C ++ (gcc, clang et autres qui prennent en charge les mêmes extensions), l'un des emplacements valides pour placer l'attribut est:
(exemple sur l'explorateur du compilateur Godbolt montrant
offsetof(GPIO, b)
= 7 octets.)la source
Pour développer les réponses de @ Clifford et @Adam Kotwasinski:
la source
Pour développer la réponse de Clifford, vous pouvez toujours supprimer les champs de bits anonymes.
Donc au lieu de
utilisation
Et puis utilisez-le comme
Malheureusement, vous aurez besoin d'autant de
EMPTY_32_X
variantes que d'octets que vous avez :( Pourtant, cela vous permet d'avoir des déclarations uniques dans votre structure.la source
#define EMPTY_32_2 EMPTY_32_1; EMPTY_32_1
et#define EMPTY_32_3 EMPTY_32_2; EMPTY_32_1
etc.Pour définir un grand espaceur sous forme de groupes de 32 bits.
la source
Je pense qu'il serait avantageux d'introduire une structure supplémentaire; ce qui peut, à son tour, résoudre le problème des entretoises.
Nommez les variantes
Bien que les espaces de noms plats soient agréables, le problème est que vous vous retrouvez avec une collection hétéroclite de champs et aucun moyen simple de passer tous les champs associés ensemble. De plus, en utilisant des structures anonymes dans une union anonyme, vous ne pouvez pas passer de références aux structures elles-mêmes, ni les utiliser comme paramètres de modèle.
Dans un premier temps, j'envisagerais donc de sortir
struct
:Et enfin, l'en-tête global:
Maintenant, je peux écrire un
void special_map0(Gpio:: Map0 volatile& map);
, ainsi qu'obtenir un aperçu rapide de toutes les architectures disponibles en un coup d'œil.Espaceurs simples
Avec la définition divisée en plusieurs en-têtes, les en-têtes sont individuellement beaucoup plus gérables.
Par conséquent, mon approche initiale pour répondre exactement à vos besoins serait de m'en tenir à des répétitions
std::uint32_t:32;
. Oui, cela ajoute quelques centaines de lignes aux lignes 8k existantes, mais comme chaque en-tête est individuellement plus petit, il peut ne pas être aussi mauvais.Si vous êtes prêt à envisager des solutions plus exotiques, cependant ...
Présentation de $.
C'est un fait peu connu qui
$
est un caractère viable pour les identificateurs C ++; c'est même un personnage de départ viable (contrairement aux chiffres).Une
$
apparition dans le code source soulèverait probablement des sourcils et$$$$
attirera certainement l'attention lors des révisions de code. C'est quelque chose dont vous pouvez facilement profiter:Vous pouvez même créer un simple «lint» comme hook de pré-validation ou dans votre CI qui recherche
$$$$
dans le code C ++ validé et rejeter de tels validations.la source
GPIO_MAP0_MODER
est vraisemblablementvolatile
.) Une référence ou une utilisation de paramètre de modèle des membres précédemment anonymes pourrait cependant être utile. Et pour le cas général des structures de remplissage, bien sûr. Mais le cas d'utilisation explique pourquoi l'OP les a laissés anonymes.$$$padding##Index_[N_];
pour rendre le nom du champ plus explicite au cas où il apparaîtrait lors de la saisie semi-automatique ou lors du débogage. (Ouzz$$$padding
pour le faire trier après lesGPIO...
noms, parce que le but de cet exercice selon l'OP est plus agréable de compléter automatiquement les noms d'emplacement d'E / S mappés en mémoire.)volatile
qualificatif de la référence, qui a été corrigé. Quant à la dénomination; Je vais le laisser à l'OP. Il existe de nombreuses variantes (padding, réservé, ...), et même le "meilleur" préfixe pour l'auto-complétion peut dépendre de l'IDE à portée de main, bien que j'apprécie l'idée de peaufiner le tri.struct
) et que lesunion
finissent par se propager partout même dans des bits spécifiques à l'architecture qui se soucierait moins des autres.Bien que je convienne que les structures ne doivent pas être utilisées pour l'accès au port d'E / S MCU, la question d'origine peut être répondue de cette manière:
Vous devrez peut-être remplacer
__attribute__((packed))
par#pragma pack
ou similaire en fonction de la syntaxe de votre compilateur.Le mélange de membres privés et publics dans une structure entraîne normalement que la disposition de la mémoire n'est plus garantie par la norme C ++. Cependant, si tous les membres non statiques d'une structure sont privés, il est toujours considéré comme une mise en page POD / standard, tout comme les structures qui les incorporent.
Pour une raison quelconque, gcc produit un avertissement si un membre d'une structure anonyme est privé, j'ai donc dû lui donner un nom. Alternativement, l'envelopper dans une autre structure anonyme supprime également l'avertissement (cela peut être un bogue).
Notez que le
spacer
membre n'est pas lui-même privé, donc les données sont toujours accessibles de cette façon:Cependant, une telle expression ressemble à un piratage évident et, espérons-le, ne serait pas utilisée sans une très bonne raison, et encore moins comme une erreur.
la source
Anti-solution.
NE FAITES PAS CELA: Mélangez les champs privés et publics.
Peut-être qu'une macro avec un compteur pour générer des noms de variables uniqie sera utile?
la source