J'écris une application en c pour un STM32F105, en utilisant gcc.
Dans le passé (avec des projets plus simples), je l' ai toujours des variables définies comme char
, int
, unsigned int
et ainsi de suite.
Je vois qu'il est courant d'utiliser les types définis dans stdint.h, tels que int8_t
, uint8_t
, uint32_t
, etc. il vrai dans plusieurs API que j'utilise, et aussi dans la bibliothèque ARM CMSIS de ST.
Je crois que je comprends pourquoi nous devrions le faire; pour permettre au compilateur de mieux optimiser l'espace mémoire. Je pense qu'il peut y avoir des raisons supplémentaires.
Cependant, en raison des règles de promotion des entiers de c, je continue de me heurter aux avertissements de conversion chaque fois que j'essaie d'ajouter deux valeurs, d'effectuer une opération au niveau du bit, etc. L'avertissement se lit comme suit conversion to 'uint16_t' from 'int' may alter its value [-Wconversion]
. La question est discutée ici et ici .
Cela ne se produit pas lors de l'utilisation de variables déclarées comme int
ou unsigned int
.
Pour donner quelques exemples, étant donné ceci:
uint16_t value16;
uint8_t value8;
Je devrais changer cela:
value16 <<= 8;
value8 += 2;
pour ça:
value16 = (uint16_t)(value16 << 8);
value8 = (uint8_t)(value8 + 2);
C'est moche, mais je peux le faire si nécessaire. Voici mes questions:
Existe-t-il un cas où la conversion de non signé en signé et de retour en non signé rendra le résultat incorrect?
Y a-t-il d'autres grandes raisons pour / contre l'utilisation des types entiers stdint.h?
Sur la base des réponses que je reçois, il semble que les types stdint.h soient généralement préférés, même si c se convertit en uint
va- int
et-vient. Cela conduit à une plus grande question:
- Je peux empêcher les avertissements du compilateur en utilisant le transtypage (par exemple
value16 = (uint16_t)(value16 << 8);
). Suis-je juste en train de cacher le problème? Y a-t-il une meilleure façon de procéder?
8u
et2u
.value8 += 2u;
etvalue8 = value8 + 2u;
, mais je reçois les mêmes avertissements.Réponses:
Un compilateur conforme aux normes où se
int
situait entre 17 et 32 bits peut légitimement faire tout ce qu'il veut avec le code suivant:Une implémentation qui le ferait pourrait légitimement générer un programme qui ne ferait rien d'autre que de sortir la chaîne "Fred" à plusieurs reprises sur chaque broche de port en utilisant tous les protocoles imaginables. La probabilité qu'un programme soit porté sur une implémentation qui ferait une telle chose est exceptionnellement faible, mais elle est théoriquement possible. Si vous souhaitez écrire le code ci-dessus afin qu'il soit garanti de ne pas s'engager dans un comportement indéfini, il serait nécessaire d'écrire cette dernière expression sous la forme
(uint32_t)x*x
ou1u*x*x
. Sur un compilateur où seint
situe entre 17 et 31 bits, cette dernière expression tronquerait les bits supérieurs, mais ne s'engagerait pas dans un comportement indéfini.Je pense que les avertissements gcc essaient probablement de suggérer que le code tel qu'il est écrit n'est pas complètement 100% portable. Il y a des moments où le code doit vraiment être écrit pour éviter les comportements qui ne seraient pas définis sur certaines implémentations, mais dans de nombreux autres cas, il faut simplement comprendre qu'il est peu probable que le code soit utilisé sur des implémentations qui feraient des choses trop ennuyeuses.
Notez que l'utilisation de types tels que
int
etshort
peut éliminer certains avertissements et résoudre certains problèmes, mais en créerait probablement d'autres. L'interaction entre des types commeuint16_t
et les règles de promotion des nombres entiers de C est épineuse, mais ces types sont probablement encore meilleurs que n'importe quelle alternative.la source
1) Si vous effectuez simplement un cast entre un entier non signé et un entier signé de la même longueur, sans aucune opération entre les deux, vous obtiendrez le même résultat à chaque fois, donc pas de problème ici. Mais diverses opérations logiques et arithmétiques agissent différemment sur les opérandes signés et non signés.
2) La principale raison d'utiliser des
stdint.h
types est que la taille en bits d'un tel type est définie et égale sur toutes les plates-formes, ce qui n'est pas vrai pourint
,long
etc., etchar
n'a pas de signe standard, il peut être signé ou non signé par défaut. Il facilite la manipulation des données en connaissant la taille exacte sans recourir à des vérifications et hypothèses supplémentaires.la source
int32_t
etuint32_t
sont égales sur toutes les plateformes sur lesquelles elles sont définies . Si le processeur n'a pas de type matériel correspondant exactement , ces types ne sont pas définis. D'où l'avantage deint
etc. et, peut-être,int_least32_t
etc.Étant donné que le # 2 d'Eugène est probablement le point le plus important, je voudrais juste ajouter que c'est un avis
Jack Ganssle semble également être partisan de cette règle: http://www.ganssle.com/tem/tem265.html
la source
uint32_t
.Un moyen simple d'éliminer les avertissements est d'éviter d'utiliser -Wconversion dans GCC. Je pense que vous devez activer cette option manuellement, mais sinon, vous pouvez utiliser -Wno-conversion pour la désactiver. Vous pouvez activer les avertissements pour les conversions de signes et de précision FP via d' autres options , si vous les souhaitez toujours.
Les avertissements de -Wconversion sont presque toujours de faux positifs, ce qui explique probablement pourquoi même -Wextra ne le permet pas par défaut. Une question Stack Overflow a beaucoup de suggestions pour de bons jeux d'options. D'après ma propre expérience, c'est un bon point de départ:
Ajoutez-en plus si vous en avez besoin, mais il y a de fortes chances que vous ne le fassiez pas.
Si vous devez conserver -Wconversion, vous pouvez raccourcir un peu votre code en ne transtypant que l'opérande numérique:
Ce n'est pas facile à lire sans mettre en évidence la syntaxe.
la source
dans tout projet logiciel, il est très important d'utiliser des définitions de type portables. (même la prochaine version du même compilateur a besoin de cette considération.) Un bon exemple, il y a plusieurs années, j'ai travaillé sur un projet où le compilateur actuel définissait 'int' comme 8bits. La prochaine version du compilateur a défini «int» comme 16 bits. Comme nous n'avions pas utilisé de définitions portables pour «int», le ram a (effectivement) doublé de taille et de nombreuses séquences de code qui dépendaient d'un int 8 bits ont échoué. L'utilisation d'une définition de type portable aurait évité ce problème (des centaines d'heures de travail à résoudre).
la source
int
pour faire référence à un type 8 bits. Même si un compilateur non-C comme CCS le fait, un code raisonnable doit utiliser soitchar
un type typedefed pour 8 bits, et un type typedefed (pas "long") pour 16 bits. D'un autre côté, le portage de code de quelque chose comme CCS vers un vrai compilateur est susceptible d'être problématique même s'il utilise des typedefs appropriés, car de tels compilateurs sont souvent "inhabituels" à d'autres égards.Oui. Un entier signé de n bits peut représenter environ la moitié du nombre de nombres non négatifs en tant qu'entier non signé de n bits, et s'appuyer sur des caractéristiques de débordement est un comportement non défini pour que tout puisse arriver. La grande majorité des processeurs actuels et passés utilisent un complément à deux, de sorte que de nombreuses opérations font la même chose sur les types intégraux signés et non signés, mais même dans ce cas, toutes les opérations ne produiront pas des résultats identiques au niveau du bit. Vous demandez vraiment des problèmes supplémentaires plus tard lorsque vous ne pouvez pas comprendre pourquoi votre code ne fonctionne pas comme prévu.
Alors que int et unsigned ont des tailles définies par l'implémentation, celles-ci sont souvent choisies "intelligemment" par l'implémentation pour des raisons de taille ou de vitesse. Je m'y tiens généralement, sauf si j'ai une bonne raison de faire autrement. De même, lorsque je décide d'utiliser int ou unsigned, je préfère généralement int sauf si j'ai une bonne raison de le faire autrement.
Dans les cas où j'ai vraiment besoin d'un meilleur contrôle de la taille ou de la signature d'un type, je préfère généralement utiliser un typedef défini par le système (size_t, intmax_t, etc.) ou créer mon propre typedef qui indique la fonction d'un élément donné type (prng_int, adc_int, etc.).
la source
Souvent, le code est utilisé sur ARM thumb et AVR (et x86, powerPC et autres architectures), et 16 ou 32 bits peuvent être plus efficaces (dans les deux sens: flash et cycles) sur STM32 ARM même pour une variable qui tient en 8 bits ( 8 bits est plus efficace sur AVR) . Cependant, si la SRAM est presque pleine, le retour à 8 bits pour les variables globales peut être raisonnable (mais pas pour les variables locales). Pour la portabilité et la maintenance (en particulier pour les vars 8 bits), il a des avantages (sans aucun inconvénient) de spécifier la taille MINIMALE appropriée , au lieu de la taille exacte et typedef à un endroit .h (généralement sous ifdef) à régler (éventuellement uint_fast8_t / uint_least8_t) pendant le portage / la construction, par exemple:
La bibliothèque GNU aide un peu, mais normalement les typedefs ont quand même un sens:
// mais uint_fast8_t pour BOTH lorsque SRAM n'est pas un problème.
la source