Notions de base du codage
Nous vous suggérons de respecter une norme de qualité minimale 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 corrigés, améliorent les performances des applications.
- Lors de la définition de l’ensemble d’API cible, nous vous recommandons d’utiliser les derniers outils CMake et Azure Sphere, puis de compiler les fichiers binaires release finaux en définissant
AZURE_SPHERE_TARGET_API_SET="latest-lts"
. Pour plus d’informations, consultez Codage pour la sécurité renouvelable.
Notes
Lorsque vous créez spécifiquement des packages d’images à charger de manière indépendante dans le cadre d’un processus de fabrication, définissez AZURE_SPHERE_TARGET_API_SET
sur la version appropriée du système d’exploitation Azure Sphere avec laquelle l’appareil a été source ou récupéré ; si vous ne le faites pas, le système d’exploitation Azure Sphere rejette le package d’image.
- Lorsque vous êtes prêt à déployer une application en production, veillez à compiler les packages d’image finaux en mode Mise en production.
- Il est courant de voir des applications déployées en production en dépit des avertissements du compilateur. L’application d’une stratégie de zéro avertissement pour les builds complètes garantit que chaque avertissement du compilateur est intentionnellement traité. Voici les types d’avertissement les plus fréquents, que nous vous recommandons vivement de traiter :
- Avertissements implicites liés à la conversion : Les bogues sont souvent introduits en raison de conversions implicites résultant d’implémentations rapides initiales qui n’ont pas été 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 la conversion sont recommandées, et pas seulement la conversion.
- Évitez de modifier les types de paramètres attendus : Lors de l’appel d’API, s’il n’est pas explicitement converti, les conversions implicites peuvent entraîner des problèmes ; par exemple, le remplacement d’une mémoire tampon lorsqu’un type numérique signé est utilisé à la place d’un type numérique non signé.
- avertissements const-discarding : Lorsqu’une fonction nécessite un type const en tant que paramètre, sa 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 CERTAINE API ou fonction.
- Avertissements de pointeur ou de paramètre incompatibles : Ignorer cet avertissement peut souvent masquer des 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 une gestion durable des applications à long terme, car elle permet facilement de recréer des fichiers binaires et leurs symboles correspondants pour le débogage d’anciennes versions d’applications. Une stratégie de branchement appropriée est également essentielle pour suivre les versions et éviter un espace disque coûteux dans le stockage des données binaires.
- Dans la mesure du possible, définissez toutes les chaînes fixes courantes comme
global const char*
au lieu de les coder en dur (par exemple, dansprintf
des commandes) afin qu’elles puissent être utilisées comme pointeurs de données dans l’ensemble du code base tout en conservant le code plus facile à gérer. Dans les applications réelles, la collecte de texte commun à partir de journaux ou de manipulations de chaînes (telles queOK
,Succeeded
ou des noms de propriétés JSON) et sa globalisation en constantes ont souvent entraîné des économies dans la section mémoire des données en lecture seule (également appelée .rodata), ce qui se traduit par des économies en 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.
Notes
L’opération ci-dessus peut également être obtenue simplement en activant les optimisations du compilateur (par -fmerge-constants
exemple, 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 selon les 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 la 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 dynamique de mémoire autant que 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 cette opération est que le retour d’un seul 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 en danger 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..])
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 des bibliothèques standard, ils doivent généralement être explicitement codés 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 conçues avec soin.
Outre les tableaux généralement alloués statiquement ou les implémentations hautement dynamiques de la mémoire, nous recommandons une approche d’allocation incrémentielle. Par exemple, commencez par une implémentation de file d’attente vide de N objets pré-alloués ; dans la file d’attente (N+1)th, la file d’attente augmente d’un X objets pré-alloués supplémentaires fixe (N=N+X), qui reste dynamiquement alloué 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 à appeler avec parcimonie (car 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 supplémentaire contre le dépassement de capacité.
Cette approche élimine le « bavardage » généré par l’allocation et la désallocation continues de la mémoire 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 des structures telles que des listes, des tableaux, etc.