OVH Cloud OVH Cloud

Cast d'une union ?

41 réponses
Avatar
Aurelien Regat-Barrel
Bonjour à tous,
Je me demandais s'il était valide de caster une union comme dans
l'exemple qui suit:

union union_t {
struct s {
short low;
short high;
} u;
long l;
};

void Example( union_t* );

void Test( long Value )
{
Example( reinterpret_cast<union_t*>( &Value ) );
}

Cet utilisation de reinterpret_cast est-elle problématique ?
Merci.

--
Aurélien Regat-Barrel

10 réponses

1 2 3 4 5
Avatar
James Kanze
Aurelien Regat-Barrel wrote:

Si ton sentiment est confirmé, ça remet en cause le miens.
Ce type d'union est pas mal utilisé pour faciliter la
manipulation des octets d'une donnée quelconque.




Par qui ? C'est un comportement indéfini de lire un élément
de l'union autre que le dernier qu'on a écrit. Ça l'a
toujours été, et en général, ça ne marche pas avec beaucoup
de compilateurs ; par exemple, le compilateur voit que la
valeur écrite dan mon_t.l n'est jamais utilisée, et supprime
l'écriture.



J'ai deux exemples en tête. Un ancien, du temps de DOS, pour
la représentation des registres du CPU:


struct WORDREGS {
unsigned int ax, bx, cx, dx, si, di, cflag, flags;
};


struct BYTEREGS {
unsigned char al, ah, bl, bh, cl, ch, dl, dh;
};


union REGS {
struct WORDREGS x;
struct BYTEREGS h;
};


exemple d'utilisation:
http://perso.wanadoo.fr/olivier.lalorette/sys.htm


Note bien en revanche qu'il s'agit ici d'un comportement
indéfini par la norme -- ce qui est un peu normal -- puisqu'en
fait, tu es en train de préparer des données pour l'assembleur.
(En ce qui concerne la norme, un compilateur est bien libre
aussi de mettre des octets de rembourage dans le struct
BYTEREGS. Ce qui ferait que ah coïncide avec le poids faible de
bx. Mais évidemment, celui qui a créé ces struct et cette union
sait bien que le compilateur en question ne le faisait pas.)

ou plus récent, dans le monde Windows, le type LARGE_INTEGER:


typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
};
struct {
DWORD LowPart;
LONG HighPart; } u;
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;



http://msdn.microsoft.com/library/en-us/winprog/winprog/large_integer_str.asp



Tu as dû perdre un nom quelque part en copiant. Tel que c'est
écrit, la première struct ne sert à rien -- je crois que le code
n'est même pas légal.

En supposant un nom derrière la première struct : sans savoir ce
que sont DWORD et LONG, c'est difficile à saisir ce qui se passe
ici. Mais j'ai comme une doute qu'il s'agit aussi d'un
comportement indéfini par la norme, défini en revanche dans une
certaine impémentation pour qui sait quelle raison.

Utilisé par SetFilePointerEx par exemple (equivalent de seek):
http://msdn.microsoft.com/library/en-us/fileio/fs/setfilepointer.asp


Le type LARGE_INTEGER est utilisé par de nombreux compilos
C/C++ sous Windows, et je pense que ça marche à chaque fois.


Si ça fait partie de l'API du système, effectivement, un
compilateur n'oserait pas ne pas le faire marcher. (Mais j'avoue
qu'une saloperie comme ça ne me donne pas envie de programmer
pour le système.)

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34



Avatar
Fabien LE LEZ
On Sun, 09 Apr 2006 20:21:42 +0200, James Kanze :

(Mais j'avoue
qu'une saloperie comme ça ne me donne pas envie de programmer
pour le système.)


Ben... si tu dois programmer sous Windows, tu feras comme tout le
monde : encapsuler les saloperies qui te sont indispensables (et Dieu
sait qu'il y en a un paquet !), histoire de pouvoir programmer
proprement dans le reste du code...

Avatar
kanze
Fabien LE LEZ wrote:
On Sun, 09 Apr 2006 20:21:42 +0200, James Kanze :

(Mais j'avoue
qu'une saloperie comme ça ne me donne pas envie de programmer
pour le système.)


Ben... si tu dois programmer sous Windows, tu feras comme tout
le monde : encapsuler les saloperies qui te sont
indispensables (et Dieu sait qu'il y en a un paquet !),
histoire de pouvoir programmer proprement dans le reste du
code...


En somme, comme on fait pour Unix. (Je ne me rappelle pas
d'avoir vue quelque chose d'aussi affreux sous Unix, mais il y a
bien des choses qu'on préfèrerait ne pas voir quand même.)

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Aurelien Regat-Barrel

ou plus récent, dans le monde Windows, le type LARGE_INTEGER:

typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
};
struct {
DWORD LowPart;
LONG HighPart; } u;
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;


http://msdn.microsoft.com/library/en-us/winprog/winprog/large_integer_str.asp



Tu as dû perdre un nom quelque part en copiant. Tel que c'est
écrit, la première struct ne sert à rien -- je crois que le code
n'est même pas légal.


Non, je n'ai rien oublié. C'est juste que le compilo de MS permettait de
définir des structs (et même des unions) anonymes. Je suppose qu'à la
base il ne devait y avoir que la première struct (anonyme), et qu'il ont
rajouté la seconde (identique mais nommée) pour que ce soit un peu plus
conforme à la norme, tout en laissant la struct anonyme pour
compatibilité ascendante.
MingW définit LARGE_INTEGER ainsi:

typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
} u;
#if ! defined(NONAMELESSUNION) || defined(__cplusplus)
_ANONYMOUS_STRUCT struct {
DWORD LowPart;
LONG HighPart;
};
#endif /* NONAMELESSUNION */
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;

et dans windef.h (de MingW toujours):

#define _ANONYMOUS_UNION __extension__

En supposant un nom derrière la première struct : sans savoir ce
que sont DWORD et LONG, c'est difficile à saisir ce qui se passe
ici.


typedef unsigned long DWORD;
typedef long LONG;
typedef __int64 LONGLONG;

Mais j'ai comme une doute qu'il s'agit aussi d'un
comportement indéfini par la norme, défini en revanche dans une
certaine impémentation pour qui sait quelle raison.


La doc de LARGE_INTERGER semble expliquer que ce type sert à manipuler
des entiers bits avec des compilateurs qui ne disposent que d'entiers 32
bits.


Je modifie ma question. Si j'ai ceci:

union union_t
{
int i;
};

je peux caster comme en suivant ?

int n = 10;
union_t *u = reinterpret_cast<union_t*>( &n );
std::cout << u->i;

Utilisé par SetFilePointerEx par exemple (equivalent de seek):
http://msdn.microsoft.com/library/en-us/fileio/fs/setfilepointer.asp

Le type LARGE_INTEGER est utilisé par de nombreux compilos
C/C++ sous Windows, et je pense que ça marche à chaque fois.


Si ça fait partie de l'API du système, effectivement, un
compilateur n'oserait pas ne pas le faire marcher. (Mais j'avoue
qu'une saloperie comme ça ne me donne pas envie de programmer
pour le système.)


Si une petite union comme ça te fait hérisser le poil, VARIANT risque de
te causer un infarctus:

http://msdn.microsoft.com/library/en-us/automat/html/e305240e-9e11-4006-98cc-26f4932d2118.asp

:-)

--
Aurélien Regat-Barrel


Avatar
Aurelien Regat-Barrel
Pour moi
l'union c'est (entre autre) un autre moyen de faire du cast.



Ben non. Un union est juste un moyen d'économiser de la place, quand
on sait qu'à un moment donné d'un programme, on n'utilisera qu'une
partie des membres.
En fait, "union" n'est rien d'autre qu'une optimisation. Si, en
remplaçant "union" par "struct" partout dans ton code, le sens du
programme change, il y a des chances pour que ton code de départ ait
un comportement indéfini.


Je savais pas.

Par ailleurs, quand tu écris :

union union_t {
struct s {
short low;
short high;
} u;
long l;
};

tu as l'air de penser que sizeof(union_t::s)==sizeof(long).
Y a-t-il un argument qui justifierait cela ?


Un padding à zéro (#pragma pack...) ?

--
Aurélien Regat-Barrel


Avatar
Fabien LE LEZ
On Mon, 10 Apr 2006 09:50:38 +0200, Aurelien Regat-Barrel
:

je peux caster comme en suivant ?

int n = 10;
union_t *u = reinterpret_cast<union_t*>( &n );
std::cout << u->i;


En C++, non.
Si maintenant tu fais du code spécifique à un compilo (ou quelques
compilateurs), p'têt' ben qu'ça marche, p'têt' ben qu'ça marche pas.
Faut voir la doc du/des compilo(s) en question.

Si une petite union comme ça te fait hérisser le poil, VARIANT risque de
te causer un infarctus:


Mon préféré est GetMessage() -- fonction utilisée, directement ou
indirectement, par à peu près toutes les applications GUI sous
Windows, et dont la doc indique :

BOOL GetMessage (...)

La valeur de retour, de type BOOL, vaut :
If the function retrieves a message other than WM_QUIT, the
return value is nonzero.
If the function retrieves the WM_QUIT message, the return value
is zero.
If there is an error, the return value is -1.

Bien évidemment, pour que ça marche, "BOOL" est en fait un typedef sur
int. Et, pour simplifier le tout, il me semble que stricto sensu, -1
est une valeur "nonzero".

Je me demande si ce truc a été proposé à l'IOCCC...

Avatar
Sylvain
Fabien LE LEZ wrote on 10/04/2006 10:05:

BOOL GetMessage (...)

La valeur de retour, de type BOOL, vaut :
If the function retrieves a message other than WM_QUIT, the
return value is nonzero.
If the function retrieves the WM_QUIT message, the return value
is zero.
If there is an error, the return value is -1.

Bien évidemment, pour que ça marche, "BOOL" est en fait un typedef sur
int. Et, pour simplifier le tout, il me semble que stricto sensu, -1
est une valeur "nonzero".



drole de façon de (ré)inventer l'histoire ?!

le type BOOL existe depuis windows 1.0 bien avant que GetMessage ne soit
modifié pour permettre la distinction d'erreur sévère, passons.

btw, en effet -1 est "nonzero", le contraire n'est ni indiqué, ni
sous-entendu.

Sylvain.

Avatar
Loïc Joly
Fabien LE LEZ wrote on 10/04/2006 10:05:


BOOL GetMessage (...)

La valeur de retour, de type BOOL, vaut :
If the function retrieves a message other than WM_QUIT, the
return value is nonzero.
If the function retrieves the WM_QUIT message, the return value
is zero. If there is an error, the return value is -1.
Bien évidemment, pour que ça marche, "BOOL" est en fait un typedef sur
int. Et, pour simplifier le tout, il me semble que stricto sensu, -1
est une valeur "nonzero".



drole de façon de (ré)inventer l'histoire ?!

le type BOOL existe depuis windows 1.0 bien avant que GetMessage ne soit
modifié pour permettre la distinction d'erreur sévère, passons.



Pourquoi ne pas modifier la fonction pour qu'elle retourne un int (un
DWORD) dans ce cas ? Ca éviterai d'induire l'utilisateur en erreur, et
ne devrait pas casser de code existant.

--
Loïc


Avatar
Sylvain
Loïc Joly wrote on 10/04/2006 23:31:

le type BOOL existe depuis windows 1.0 bien avant que GetMessage ne
soit modifié pour permettre la distinction d'erreur sévère, passons.


Pourquoi ne pas modifier la fonction pour qu'elle retourne un int (un
DWORD) dans ce cas ? Ca éviterai d'induire l'utilisateur en erreur, et
ne devrait pas casser de code existant.



un "int" (type aliasé par "BOOL") est sur Intel 32 bits un type entier
signé sur 32 bits, un DWORD est un "unsigned long", ça fait déjà une
différence.

modifier le type retour d'une API préhistorique n'aurait pas été
imaginable (problème de compatibilité asc. même si l'impact est ici
assez faible, pas pire que le fait de ne pas traiter ces retours négatifs).

il aurait été plus propre d'avoir un GetMessageEx(...) qui lui (et lui
seul) aurait traité des erreurs critiques; mais M$ a ses raisons que la
raison et l'élégance souvent ignorent.

Sylvain.


Avatar
Loïc Joly
Loïc Joly wrote on 10/04/2006 23:31:


le type BOOL existe depuis windows 1.0 bien avant que GetMessage ne
soit modifié pour permettre la distinction d'erreur sévère, passons.



Pourquoi ne pas modifier la fonction pour qu'elle retourne un int (un
DWORD) dans ce cas ? Ca éviterai d'induire l'utilisateur en erreur, et
ne devrait pas casser de code existant.



un "int" (type aliasé par "BOOL") est sur Intel 32 bits un type entier
signé sur 32 bits, un DWORD est un "unsigned long", ça fait déjà une
différence.


Oups, pas fais gaffe à l'aspect signé. INT alors.


modifier le type retour d'une API préhistorique n'aurait pas été
imaginable (problème de compatibilité asc. même si l'impact est ici
assez faible, pas pire que le fait de ne pas traiter ces retours négatifs).


Vu que INT et BOOL sont définis de même manière, je ne vois aucun impact
pour les utilisateurs.

--
Loïc



1 2 3 4 5