Notions de base du codage
Important
Il s’agit de la documentation Azure Sphere (héritée). Azure Sphere (hérité) prend sa retraite le 27 septembre 2027 et les utilisateurs doivent migrer vers Azure Sphere (intégré) pour l’instant. Utilisez le sélecteur de version situé au-dessus du TOC pour afficher la documentation Azure Sphere (intégrée).
Nous vous suggérons de respecter une norme de qualité minimale telle que définie dans cette rubrique. Grâce à nos partenariats avec les clients qui cherchent à améliorer leurs applications déployées en production, nous avons détecté certains problèmes courants, qui, lorsqu’ils sont résolus, améliorent les performances des applications.
Problèmes courants
- Lors de la définition de l’ensemble d’API cible, nous vous recommandons d’utiliser les outils CMake et Azure Sphere les plus récents, et finalement de compiler les fichiers binaires de version finale en définissant
AZURE_SPHERE_TARGET_API_SET="latest-lts"
. Pour plus d’informations, consultez Codage pour la sécurité renouvelable.
Remarque
Lors de la création spécifique de packages d’images à charger de manière indépendante au sein d’un processus de fabrication, définissez AZURE_SPHERE_TARGET_API_SET
la version appropriée du système d’exploitation Azure Sphere avec laquelle l’appareil a été source ou récupéré ; l’échec de cette opération entraîne le rejet du package d’image par le système d’exploitation Azure Sphere.
- Lorsque vous êtes prêt à déployer une application en production, veillez à compiler les packages d’images finaux en mode Mise en production.
- Il est courant de voir les applications déployées en production malgré les avertissements du compilateur. L’application d’une stratégie d’avertissements zéro pour les builds complètes garantit que chaque avertissement du compilateur est intentionnellement traité. Voici les types d’avertissements les plus fréquents, que nous vous recommandons vivement d’adresser :
- Avertissements liés à la conversion implicite : les bogues sont souvent introduits en raison de conversions implicites résultant d’implémentations initiales et rapides qui restent non supervisées. Par exemple, le code qui a de nombreuses conversions numériques implicites entre différents types numériques peut entraîner une perte de précision critique ou même des erreurs de calcul ou de branchement. Pour ajuster correctement tous les types numériques, l’analyse intentionnelle et le cast sont recommandés, pas seulement le cast.
- Évitez de modifier les types de paramètres attendus : lorsque vous appelez des API, si ce n’est pas explicitement le cas, les conversions implicites peuvent entraîner des problèmes ; par exemple, la surexécution d’une mémoire tampon lorsqu’un type numérique signé est utilisé au lieu d’un type numérique non signé.
- avertissements const-discarding : lorsqu’une fonction requiert un type const en tant que paramètre, la substitution peut entraîner des bogues et un comportement imprévisible. La raison de l’avertissement est de s’assurer que le paramètre const reste intact et prend en compte les restrictions lors de la conception d’une API ou d’une fonction spécifique.
- Avertissements de pointeur ou de paramètre incompatibles : l’ignorance de cet avertissement peut souvent masquer les bogues qui seront difficiles à suivre ultérieurement. L’élimination de ces avertissements peut aider à réduire le temps de diagnostic d’autres problèmes d’application.
- La configuration d’un pipeline CI/CD cohérent est essentielle pour la gestion durable des applications à long terme, car elle permet facilement de recréer des fichiers binaires et leurs symboles correspondants pour le débogage des versions antérieures de l’application. Une stratégie de branchement appropriée est également essentielle pour le suivi des versions et évite l’espace disque coûteux dans le stockage des données binaires.
Problèmes liés à la mémoire
- Si possible, définissez toutes les chaînes fixes courantes au
global const char*
lieu de les coder en dur (par exemple, dansprintf
les commandes) afin qu’elles puissent être utilisées en tant que pointeurs de données tout au long de la base de code tout en conservant le code plus facile à gérer. Dans les applications réelles, la collecte de texte commun à partir de journaux d’activité ou de manipulations de chaînes (parOK
exemple, ,Succeeded
ou noms de propriétés JSON) et la globalisation de celles-ci en constantes a souvent entraîné des économies dans la section mémoire de données en lecture seule (également appelée .rodata), ce qui se traduit par des économies de mémoire flash qui pourraient être utilisées dans d’autres sections (comme .text pour plus de code). Ce scénario est souvent négligé, mais il peut générer des économies significatives en mémoire flash.
Remarque
Les éléments ci-dessus peuvent également être obtenus simplement en activant des optimisations du compilateur (telles que -fmerge-constants
sur gcc). Si vous choisissez cette approche, inspectez également la sortie du compilateur et vérifiez que les optimisations souhaitées ont été appliquées, car elles peuvent varier entre différentes versions du compilateur.
- Pour les structures de données globales, dans la mesure du possible, envisagez de donner des longueurs fixes à des membres de tableau raisonnablement petits plutôt que d’utiliser des pointeurs vers une mémoire allouée dynamiquement. Par exemple :
typedef struct {
int chID;
...
char chName[SIZEOF_CHANNEL_NAME]; // This approach is preferable, and easier to use e.g. in a function stack.
char *chName; // Unless this points to a constant, tracking a memory buffer introduces more complexity, to be weighed with the cost/benefit, especially when using multiple instances of the structure.
...
} myConfig;
- Évitez l’allocation de mémoire dynamique dans la mesure du possible, en particulier dans les fonctions fréquemment appelées.
- En C, recherchez les fonctions qui retournent un pointeur vers une mémoire tampon et envisagez de les convertir en fonctions qui retournent un pointeur de mémoire tampon référencé et sa taille associée aux appelants. La raison de cela est que le renvoi d’un simple pointeur vers une mémoire tampon a souvent entraîné des problèmes avec le code appelant, car la taille de la mémoire tampon retournée n’est pas reconnue de force et peut donc mettre à risque la cohérence du tas. Par exemple :
// This approach is preferable:
MY_RESULT_TYPE getBuffer(void **ptr, size_t &size, [...other parameters..])
// This should be avoided, as it lacks tracking the size of the returned buffer and a dedicated result code:
void *getBuffer([...other parameters..])
Conteneurs dynamiques et mémoires tampons
Les conteneurs tels que les listes et les vecteurs sont également fréquemment utilisés dans les applications C incorporées, avec la mise en garde qu’en raison des limitations de mémoire dans l’utilisation de bibliothèques standard, ils doivent généralement être codés explicitement ou liés en tant que bibliothèques. Ces implémentations de bibliothèque peuvent déclencher une utilisation intensive de la mémoire si elles ne sont pas soigneusement conçues.
En plus des tableaux alloués statiquement ou des implémentations hautement dynamiques en mémoire, nous vous recommandons d’adopter une approche d’allocation incrémentielle. Par exemple, commencez par une implémentation de file d’attente vide d’objets pré-alloués N ; sur le push de la file d’attente (N+1), la file d’attente augmente par des objets pré-alloués X fixes (N=N+X), qui resteront alloués dynamiquement jusqu’à ce qu’un autre ajout à la file d’attente dépasse sa capacité actuelle et incrémente l’allocation de mémoire par X objets pré-alloués supplémentaires. Vous pouvez éventuellement implémenter une nouvelle fonction de compactage pour appeler avec parcimonie (comme il serait trop coûteux d’appeler régulièrement) pour récupérer la mémoire inutilisée.
Un index dédié conserve dynamiquement le nombre d’objets actifs pour la file d’attente, qui peut être limité à une valeur maximale pour une protection de dépassement de capacité supplémentaire.
Cette approche élimine le « chatter » généré par l’allocation de mémoire continue et la désallocation dans les implémentations de file d’attente traditionnelles. Pour plus d’informations, consultez Gestion et utilisation de la mémoire. Vous pouvez implémenter des approches similaires pour les structures telles que les listes, les tableaux, etc.