OVH Cloud OVH Cloud

Travailler avec des bytes en C

48 réponses
Avatar
simon.bree
Bonjour,

J'ai un buffer de byte en C.

J'ai trouver sous java que pour transformer un byte en entier on=20
utilise :
(buffer[0] & 0xff) | ((buffer[1] & 0xff) << 8) | ((buffer[2] & 0xff)=20
<< 16) | (buffer[3] << 24)

Estg-ce la m=EAme chose en C? Et si oui, pourriez-vous m'expliquer cette=20
syntaxe?

Merci d'avance,
Simon

10 réponses

1 2 3 4 5
Avatar
espie
In article <epgjd2$uri$,
Xavier Roche wrote:
uint8_t est justement introduit dans la norme pour définir un entier non
signé 8 bits. Le "unsigned char" n'est pas forcément 8 bits.


Mouarf. Bon, oui, d'accord. Ce que je voulais dire, c'est que pour un
débutant, sizeof(char) == 1.

C'est comme sizeof(void*) != sizeof(void (*)(void)): oui, ça existe.
Mais pour un débutant, il vaut mieux l'oublier, cela ne sert à rien de
complexifier à l'extrème les choses.


Pas d'accord du tout. Le debutant, il vaut mieux qu'il evite de melanger
tout et n'importe quoi.

Moi je t'avoue, j'en ai *marre* de corriger du code de debutant monte
en pousse qui me fout des void* dans des int, parce que vois-tu, c'est
la meme taille de toutes facons, ou qui considere que les char sont
toujours signes... j'en passe et des meilleures.

Plus tot le debutant prendra quelques bons reflexes, comme d'appeler un
chat un chat, mieux ca vaudra pour tout le monde.


Avatar
Xavier Roche
Moi je t'avoue, j'en ai *marre* de corriger du code de debutant monte
en pousse qui me fout des void* dans des int


Je reformule: dan la vraie vie, on a souvent sizeof(void*) !=
sizeof(int), et il est en effet important de le signaler (de même que
sizeof(long) n'est pas forcément >= sizeof(void*))

Dans le cas du sizeof((signed|unsigned) char), c'est tellement rare que
dans la pratique les quelques cas spécifiques (à la DSP, par exemple)
seront de toute manière tellement bien cadrés que cela ne posera pa de
problèmes.

Plus tot le debutant prendra quelques bons reflexes, comme d'appeler un
chat un chat, mieux ca vaudra pour tout le monde.


D'un autre côté le surcharger avec des notions qu'il n'utilisera
probablement jamais (je veux dire, un informaticien de base ne touchera
jamais à autre chose qu'à des processeurs de pc ou de serveur) n'est
peut être pas utile pour l'instant

Avatar
espie
In article <ephmnq$o00$,
Xavier Roche wrote:
D'un autre côté le surcharger avec des notions qu'il n'utilisera
probablement jamais (je veux dire, un informaticien de base ne touchera
jamais à autre chose qu'à des processeurs de pc ou de serveur) n'est
peut être pas utile pour l'instant


Bof... je te rappelle que toutes les merdouilles de int != void* qu'on
voit aujourd'hui correspondent directement a du code ecrit il y a 5 a 10
ans, pendant la grande proliferation des processeurs 32 bits, ou les
archis n'ayant pas la meme taille pour les deux etaient considerees comme
exotiques...

En fait, pour preciser le fond de ma pensee, je trouve qu'on explique
certaines notions avancees aux debutants bien trop tot. On ne leur dit
souvent pas de faire tres tres attention aux cast, ou que les union ne
servent a peu pres rien. Par exemple, je trouve relativement stupide
d'expliquer aux etudiants qu'on peut ecrire une struct directement sur
disque avant de leur avoir explique ce que ca implique.

Il semble y avoir une notion qui est que `c'est pas grave de donner des
approximations de la realite aux debutants, on corrigera plus tard'. Comme
les int qui font 32 bits, par exemple. Le probleme, c'est qu'une proportion
non negligeable des debutants ne vont que retenir ca, parce que c'est plus
simple, ou bien qu'ils vont carrement s'arreter avant d'apprendre la
realite. On retrouve meme ces conneries dans des bouquins. A un moment,
il faut arreter. Le C moderne est un langage type. Il y a quelques cas
qui necessitent des cast, mais la premiere chose a faire quand on a envie
de mettre un cast quelque part, c'est de verifier qu'on comprend totalement
ce qui se passe, et de voir si on ne peut pas faire plus propre et sans
cast.

Je pense egalement aux optimisations dont gcc est friand, sur les
equivalences strictes entre type, prevues par la norme, et qui ont plombe
pas mal de code ecrit avant...

Avatar
Pierre Maurette
Merci Eric pour les précisions sur les char, byte et octet.

Moi, je défini mon buffer ainsi :
uint8_t buf[1024];


Les uintN_t font exactement N bits sans padding.
Les intN_t font exactement N bits sans padding et les négatifs sont en
complément à 2.
J'imagine que ces types ne sont proposés que sur des architectures dans
lesquelle ces caractéristiques sont natives. A l'exception des grandes
largeurs, par exemple un int64_t en x86-32 (les mots de 64 bits y sont
quand même efficacement supportés). J'ai quand même un doute, pour gcc
par exemple.

Est-ce donc correct, et


Oui, je pense.

- si non, comment je dois le déclaré (car buf[1024]?) ?


Personnellement, avant de découvrir les intN_t, je préférais (enfin,
quand j'étrais rigoureux) utiliser des typedef pour ne garder char que
pour des caractères (des lettres) et nommer de façon parlante selon le
sens, les types arithmétiques signés et non signés, et les "cases
mémoire" non signés.
Par exemple
typedef unsigned char octet_t;
typedef unsigned char uByte_t;
typedef unsigned char sByte_t;

- si oui, est-ce bien utile que j'utilise le masque 0xff (puisque
uint8_t est un octet) ?


Non.

Le seul piège pourrait être dans le cas où le type d'arrivée soit sur
32 bits signé, si buffer[3] est plus grand que 127 ou 0x7F, bref si son
MSB est allumé. Puisque vous auriez un dépassement de capacité sur un
signé par multiplication par 0x1000. Mais le choix d'un signé serait à
priori une erreur, et en plus je ne vois pas ce qui pourrait arriver
d'autre que ce que vous attendez sur les machines que je connais.

--
Pierre Maurette

Avatar
Pierre Maurette
Moi je t'avoue, j'en ai *marre* de corriger du code de debutant monte
en pousse qui me fout des void* dans des int


Je reformule: dan la vraie vie, on a souvent sizeof(void*) != sizeof(int), et
il est en effet important de le signaler (de même que sizeof(long) n'est pas
forcément >= sizeof(void*))


A toute chose malheur est bon. Il est aujourd'hui facile (en changeant
de cible par exemple) de proposer une manip qui mette en évidence le
danger de telles suppositions (non à la programmation suppositoire !)
et le fait qu'il y a de bonnes solutions aussi simples que les
mauvaise.
Pourquoi ne pas introduire rapidement le type intptr_t ? Puisque quand
on l'a décrit en une phrase, on a également affirmé qu'on ne devait pas
faire les suppositions évoquées plus haut. Je veux dire que même si le
type n'est pas disponible sur telle ou telle implémentation, son
existence montre la nécessité d'en créer un équivalent.
Petit danger toutefois: l'existence dans la norme de intptr_t peut être
en contradiction avec d'autres conseils de bon sens déconseillant le
stockage de pointeurs dans des entiers.
Disons que ça sert à faire de l'arthmétique sur les pointeurs sans
l'arithmétique des pointeurs. Personnellement - mais je comprends
l'opinion contraire, selon le cursus en particulier - j'aime bien
l'étudiant bricoleur qui va découper les mots et regarder sur l'entier
sous-jacent les effets de l'arithmétique des pointeurs.

Dans le cas du sizeof((signed|unsigned) char), c'est tellement rare que dans
la pratique les quelques cas spécifiques (à la DSP, par exemple) seront de
toute manière tellement bien cadrés que cela ne posera pa de problèmes.

Plus tot le debutant prendra quelques bons reflexes, comme d'appeler un
chat un chat, mieux ca vaudra pour tout le monde.


D'un autre côté le surcharger avec des notions qu'il n'utilisera probablement
jamais (je veux dire, un informaticien de base ne touchera jamais à autre
chose qu'à des processeurs de pc ou de serveur) n'est peut être pas utile
pour l'instant


Les étudiants électroniciens ou automaticiens n'apprendront peut-être
pas de C de la même façon que des informaticiens durs. Mais il ne faut
pas pour autant faire n'importe quoi, il me semble. C'est ce que j'ai
longtemps fait en tant qu'autodidacte. L'étudiant va faire du bon
travail en microcontrôleur. Il va en profiter pour se faire quelques
outils en C (peut-être au détriment de la productivité. Autant si
possible qu'il puisse utiliser et communiquer ces programmes sur
diverses machine.
Le danger inverse serait de faire de la portabilité une religion, alors
qu'il s'agit d'une caractéristique du cahier des charges et un but à
atteindre quand c'est possible et raisonnablement coûteux. PArce qu'un
partie des applications de C auxquelles seront confrontés nos étudiants
est justement d'écrire, parfois aux frontières d'autres langages, des
couches par essence non portables. Bien entendu, c'est le plus souvent
le contraire, la majorité des lignes d'un programme en C seraont les
couches portables.

--
Pierre Maurette


Avatar
espie
In article ,
Pierre Maurette wrote:

J'imagine que ces types ne sont proposés que sur des architectures dans
lesquelle ces caractéristiques sont natives. A l'exception des grandes
largeurs, par exemple un int64_t en x86-32 (les mots de 64 bits y sont
quand même efficacement supportés). J'ai quand même un doute, pour gcc
par exemple.


La norme dit explicitement que ces types doivent exister a partir du
moment ou l'implementation supporte les caracteristiques correspondantes,
mais ne sont pas garantis sinon.

Par contre, il existe une deuxieme variete de types, qui eux sont garantis,
ce sont les int_leastN_t (et uint_leastN_t), qui doivent exister en version
8, 16, 32 et 64 bits, et qui correspondent a des entiers qui font au moins
une taille minimale.

intptr_t et uintptr_t sont optionnels.

Par contre, intmax_t et uintmax_t (les plus grand nombres possibles) sont
requis.

A tous ces types, correspondent des macros qui donnent les limites
correspondantes, comme INT8_MAX, INTPTR_MAX. On peut se demander a quoi sert
INT8_MAX, puisque c'est de toutes facons 127... Mais c'est une facon de
verifier que le type int8_t existe, et d'ecrire le code conditionnel qui
va avec.

Apres, il faut savoir qui definit quoi. Dans un systeme `unifie' pas de
problemes. Sur les systemes actuels, entre l'ABI du processeur, plus ou
moins bien definie, l'OS et ses appels systeme, et le compilateur, ca
n'est pas toujours facile de savoir qui va definir quoi.

Personnellement, avant de découvrir les intN_t, je préférais (enfin,
quand j'étrais rigoureux) utiliser des typedef pour ne garder char que
pour des caractères (des lettres) et nommer de façon parlante selon le
sens, les types arithmétiques signés et non signés, et les "cases
mémoire" non signés.
Par exemple
typedef unsigned char octet_t;
typedef unsigned char uByte_t;
typedef unsigned char sByte_t;


Atttention, tout ceci n'est pas du tout rigoureux. Il faut quand meme
rappeler que la norme reserve les noms de typedef en *_t pour l'interface
avec le systeme, et donc que tu risques la collision!

Pour avoir joue avec du C un certain temps avant l'avenement de C99, je ne
peux qu'etre tres content de l'existence de stdint.h. Le probleme qui se
posait avant, ce n'etait pas du tout que ces types etaient compliques a
definir, c'est surtout que chacun faisait sa sauce dans son coin, et que
tu te retrouvais regulierement avec des definitions des memes types avec
des noms differents, et souvent de subtiles differences qui faisaient
planter ta compilation (entre les adeptes de typedef et les adeptes de
#define, par exemple)... je me suis deja retrouve a essayer de debugguer
a distance un probleme de compilation sur un systeme autre que ceux que
j'avais sous la main, parce que j'avais utilise une bibliotheque tierce
(sans doute un machine gnu, mais je suis mauvaise langue)
qui redefinissait un int_32_t quelconque, et que ca marchait presque
partout, sauf sur une implementation specifique d'Unix (vraisemblablement
IRIX ou HP-UX, mais je suis mauvaise langue) qui avait quelque chose de
similaire mais different dans un de ces entetes systemes fondamentaux.

Nota: j'en suis encore a debugguer des craditudes du meme ordre dans
des bibliotheques gnu, d'ailleurs, les machins a la gettext et iconv ont
une facheuse tendance a se croire tout permis et a faire ami-ami avec la
libc comme s'ils etaient sur un systeme Linux avec glibc: redefinition de
wchar_t et maux de tetes en perspective...

Avatar
Radamanthe
wrote:
Merci Xavier et Eric pour vos réponses rapides.

Ok, j'ai compris le << (décalage) et le | (ou logique). Mais pas trop
le & 0xff.

J'imagine que le &, c'est le "et", mais en quoi cela masque les 8 bits
de poids faible d'en entier?


Le & est un "et arithmétique". Les 8 bits de poids faible sont
représentés par la valeur 0xff.

En Java, il n'y a pas de mot clé "signed". Le signe de tous les types
entiers est signé, sauf "char" qui est un 16 bits non-signé (voir
http://en.wikibooks.org/wiki/Java_Programming/Primitive_Types, ce n'est
pas une norme mais ça a le mérite d'être assez clair).

Dans le contexte de ton exemple :

J'ai trouver sous java que pour transformer un byte en entier on
utilise :
(buffer[0] & 0xff) | ((buffer[1] & 0xff) << 8) | ((buffer[2] & 0xff)
<< 16) | (buffer[3] << 24)


Si buffer est un byte[], l'expression buffer[x] est convertie en int (de
la conversion du byte lu dans l'intervalle [-128,127]). Si le tableau
contient en réalité des valeurs non-signées (intervalle [0,255]), faire
"& 0xff" assure que le signe de l'entier est supprimé.

Pour moi, un byte, c'est 8 bits, donc un entier entre 0 et 255.


Non. Attention à la terminologie différente d'un langage à l'autre !

Un "byte" est un terme anglais qui se traduit en français par
"multiplet" et signifie "plus petit élément adressable".
Un 8 bits est un "octet" en anglais ET en français. Il s'avère juste que
dans la majorité des architectures (mais pas toutes), un byte est aussi
un octet.

En C, le mot clé "byte" n'existe pas, mais "char" remplit le même rôle.

En Java, le mot clé "byte" designe un 8 bits signé (c'est donc en fait,
un octet). De là à conclure que Java propage des idées reçues...

Un byte en C, c'est bien équivalement à un char?


Oui. Mais ce n'est pas le "byte" de Java, c'est le byte officiel.


--
R.N.

Avatar
Vincent Lefevre
Dans l'article <ephmnq$o00$,
Xavier Roche écrit:

Dans le cas du sizeof((signed|unsigned) char), c'est tellement rare que
dans la pratique les quelques cas spécifiques (à la DSP, par exemple)
seront de toute manière tellement bien cadrés que cela ne posera pa de
problèmes.


Qu'est-ce qui est tellement rare?

D'un autre côté le surcharger avec des notions qu'il n'utilisera
probablement jamais (je veux dire, un informaticien de base ne touchera
jamais à autre chose qu'à des processeurs de pc ou de serveur) n'est
peut être pas utile pour l'instant


Tu oublies que les processeurs de PC évoluent. Je ne pense pas qu'on
puisse dire quoi que ce soit des processeurs qu'on aura dans 30 ans.

--
Vincent Lefèvre - Web: <http://www.vinc17.org/>
100% accessible validated (X)HTML - Blog: <http://www.vinc17.org/blog/>
Work: CR INRIA - computer arithmetic / Arenaire project (LIP, ENS-Lyon)

Avatar
Antoine Leca
écrivit dans
news::
J'ai trouver sous java que pour transformer un byte en entier on
utilise :
(buffer[0] & 0xff) | ((buffer[1] & 0xff) << 8) | ((buffer[2] & 0xff)
<< 16) | (buffer[3] << 24)

Estg-ce la même chose en C?


Est-ce que tu as essayé ?
Si non, pourquoi ?


Et si oui, pourriez-vous m'expliquer cette syntaxe?


Qu'est-ce que tu ne comprends pas ?
l'algorithme : on passe d'une représentation sur 4 chiffres en base 256 (les
octets) à la valeur représentée
[] : opérateur d'indexation
0,1 : ordre des octets (ici petit boutient, typique d'une machine Intel)
& : opérateur de masque
0xff : 8 bits à 1
() : parenthèses parfois utiles du fait de la préséance de << sur &, souvent
inutiles
| : (ici) opérateur d'addition
<< : (ici) application de la base à la «bonne» puissance
8,16 : les multiples de 8 (<<1 vaut pour la base 2, et log2(256)vaut 8)


Antoine

Avatar
Antoine Leca
Xavier Roche écrivit dans news:epfeuo$uri$:
(buffer[0] & 0xff) | ((buffer[1] & 0xff) << 8) | ((buffer[2] & 0xff)
<< 16) | (buffer[3] << 24)


Déja, il faut savoir que cette représentation est en little endian (le
"petit bout", càd les octets de poids les plus faibles, sont en
premier) et ne sera pas toujours compatible selon les cas avec la
plate-forme voulue.


Oui

Disons que, dans des représentations réseau,
c'est effectivement le plus courant.


Ah bon ?


Sinon le "& 0xff" qui masque les 8 premiers bits ne sert pas à grand
chose (buffer[n] est déja sur 8 bits),


Ah bon ?


par contre un cast de chaque octet en uint avant le décalage me
parait sécurisant,


Cela plante l'algorithme si buffer est de type unsigned long*


dans la mesure
où le type résultant d'un (unsigned char)<<n n'est pas à priori
forcément un int..


Où a-t-il écrit cette opération ?
Et par ailleurs, sur une machine où (unsigned char)x<<n n'est pas un int,
c'est forcément un unsigned int, donc j'ai du mal à voir l'intérêt...


Antoine


1 2 3 4 5