5
votes

Réinterprétation portable de uint8_t comme int8_t et forçage du complément à deux

J'essaye de réinterpréter un uint8_t comme un int8_t (et inversement) d'une manière portable. Je suis comme je reçois sur un canal série que je stocke dans un tampon de uint8_t, mais une fois que je sais de quel type de paquet il s'agit, je dois interpréter certains octets comme un compliment à deux et d'autres comme non signés.

Je sais que cela fonctionnera sur de nombreux compilateurs:

int8_t i8;
uint8_t u8;

i8 = (u8 > INT8_MAX) ? (int8_t)(-(256-u8)):(int8_t)u8;

Mais il n'est pas garanti de fonctionner lorsque u8> 127 car le cast d'une valeur supérieure à INT8_MAX en int8_t est indéfini (je pense ).

Le mieux que j'ai pu trouver est celui-ci

int8_t i8;
uint8_t u8 = 0x94;

i8 = (int8_t)u8;

Cela devrait toujours fonctionner car la soustraction entraînera toujours une promotion automatique à int, et ne repose en aucun cas sur les représentations sous-jacentes. Cela force implicitement l'interprétation d'un complément à deux des valeurs supérieures à INT8_MAX.

Y a-t-il une meilleure façon (ou une MACRO standard) de faire cela?


1 commentaires

J'allais proposer un jeu de mots avec un caractère signé : i8 = (int8_t) * (signe signé *) & u8; , mais cela peut ne pas fonctionner sur un hypothétique plate-forme qui n'utilise pas le complément à deux pour ses représentations de type entier négatif.


3 Réponses :


8
votes

Si int8_t est défini (par ), il est garanti qu'il s'agira d'un complément à deux (par C 2018 7.20.1.1).

La valeur de uint8_t u8 peut être réinterprétée comme une valeur de complément à deux en la copiant dans int8_t i8 avec memcpy (& i8, & u8, sizeof i8); . (Les bons compilateurs optimiseront cela pour utiliser simplement u8 comme valeur de complément à deux, sans appel à memcpy .)


2 commentaires

Fantastique! Je suis heureux d'apprendre que int8_t tel que défini par stdint.h est garanti un compliment à deux! Puis-je supposer que cela vaut pour tous les types intN_t (peut-être pas rapide ou le moins)?


@Chris: Ouais, cppreference indique le complément à 2 pour tout int ## _ t types (aucune garantie pour rapide / moins ). Il peut vraisemblablement offrir cette garantie car les types non rapides / les moins rapides sont facultatifs; si le système ne supporte pas les types de cette taille ou les types ne sont pas le complément de 2, il ne les fournira tout simplement pas.



-1
votes

i8 = u8; et i8 = * (int8_t *) & u8; fonctionneront sur n'importe quel système qui existe réellement et offrent le code int8_t tapez.

Ils s'appuient sur des choix définis par l'implémentation dans les deux cas, mais personne n'utiliserait une implémentation qui n'aurait pas fait les choix évidents dans ces cas (en grande partie parce qu'une grande partie du code existant repose également sur ces choix). Il y a la portabilité, puis il y a la manipulation de votre code pour prendre en charge des systèmes qui n'existeront jamais.


2 commentaires

Coin: i8 = * (int8_t *) & u8 échoue lorsque u8 est enregistré.


@chux si déclaré register mais il n'y a aucune raison de le faire de nos jours (l'optimiseur peut et utilisera des registres même en présence de ce code)



2
votes

Dans le complément à deux de huit bits, le bit de signe peut être interprété comme ayant la valeur de position -2 8 , qui est bien sûr -256. C'est précisément ainsi que le standard C le caractérise. Par conséquent, étant donné une valeur de 8 bits stockée dans un uint8_t que vous souhaitez réinterpréter comme un entier complémentaire à deux, c'est une manière arithmétique de le faire:

uint8_t u8 = /* ... */;
int8_t  i8 = *(int8_t *)&u8;

Notez que toute l'arithmétique est effectuée en promouvant d'abord les opérandes en int (signé), donc il n'y a ni débordement (car la plage de int est assez grande pour cela) ni un bouclage arithmétique non signé. Le résultat arithmétique est garanti dans la plage de int8_t , il n'y a donc pas non plus de risque de débordement dans la conversion du résultat vers ce type.

Vous noterez des similitudes entre ce calcul et le vôtre, mais celui-ci évite l'opérateur ternaire en utilisant le résultat de l'expression relationnelle u8> 0x7f (soit 0 soit 1) directement dans l'arithmétique, évitant ainsi tout branchement, et il dispense avec des moulages inutiles. (Le vôtre n'a pas non plus besoin des transtypages.)

Notez également que si vous rencontrez une implémentation étrange qui ne fournit pas int8_t (car son char code> s sont plus larges que 8 bits, ou ses caractères signés n'utilisent pas de complément à deux) alors cette approche arithmétique fonctionne toujours dans le sens du calcul de la bonne valeur, et vous pouvez être certain d'enregistrer en toute sécurité cette valeur dans un int ou short . Ainsi, le moyen le plus portable d'extraire la valeur de l'interprétation du complément à deux de 8 bits d'un uint8_t serait

uint8_t u8 = /* ... */;
int i8 = (u8 & 0x7f) - (u8 > 0x7f) * 0x100;

Alternativement, si vous êtes prêt à s'appuyer sur int8_t pour être un type de caractère - ie un alias pour char ou signe signé - alors il est parfaitement standard de faire le travail de cette façon:

uint8_t u8 = /* ... */;
int8_t  i8 = (u8 & 0x7f) - (u8 > 0x7f) * 0x100;

Celui-ci est encore plus susceptible d'être optimisé par un compilateur que le memcpy () alternative présentée dans une autre réponse, mais contrairement à l'alternative memcpy , celle-ci a formellement un comportement indéfini si int8_t s'avère pas être un type de caractère. D'un autre côté, cela et l'approche memcpy () dépendent de l'implémentation pour fournir le type int8_t , et encore plus improbable qu'une implémentation ne fournissant pas int8_t est qu'une implémentation fournit un int8_t qui ne parvient pas à être un type de caractère.


2 commentaires

"comportement si int8_t s'avère ne pas être un type de caractère" -> Je ne vois aucune possibilité que cela puisse être vrai.


@chux, cela peut être vrai si le caractère signé de l'implémentation a un signe / magnitude de 8 bits ou une représentation de complément à un, mais l'implémentation également fournit des données de complément à deux de 8 bits type en tant que type entier signé étendu.