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
kanze
Aurelien Regat-Barrel wrote:

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_s tr.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.


Les unions anonymes, ça fait partie du langage. Les structs
anonymes, non, mais je crois que Microsoft n'est pas le seul à
les supporter comme extension. Seulement, avoir une struct
anonyme, et une autre non, et que les deux aient les mêmes noms
de membre... Ça me semble un peu louche.

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.


Deux structs anonymes ayant les mêmes noms de membre ne peut pas
aller. Logiquement. Si tu utilisais un des noms, de quelle
struct est-ce qu'il faut le tirer.

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__


Compatibilité Microsoft oblige:-).

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;


Formellement, non. Dans la pratique, oui. Mais pourquoi ?

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,


Ce n'est pas l'union en soi. C'est l'idée qu'on ferait un tel
michemache d'unions et de structs avec des noms aussi peu
parlants. Je n'en vois pas l'intérêt.

--
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

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.



Les unions anonymes, ça fait partie du langage. Les structs
anonymes, non, mais je crois que Microsoft n'est pas le seul à
les supporter comme extension. Seulement, avoir une struct
anonyme, et une autre non, et que les deux aient les mêmes noms
de membre... Ça me semble un peu louche.


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.



Deux structs anonymes ayant les mêmes noms de membre ne peut pas
aller. Logiquement. Si tu utilisais un des noms, de quelle
struct est-ce qu'il faut le tirer.


La 2° struct n'est pas anonyme.

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;



Formellement, non. Dans la pratique, oui. Mais pourquoi ?


Pour sucharger SetFilePointerEx ainsi par exemple:

bool SetFilePointerEx( LONGLONG DistanceToMove )
{
return ::SetFilePointerEx(
*reinterpret_cast<LARGE_INTEGER*>( &DistanceToMove )
) == TRUE;
}

au lieu de:

bool SetFilePointerEx( LONGLONG DistanceToMove )
{
LARGE_INTEGER tmp;
tmp.QuadPart = DistanceToMove;

return ::SetFilePointerEx( tmp ) == TRUE;
}

pour info j'ai retenu la 2° écriture, mais je me demandais si la
première était valable.

Dernière question : peut-on initialiser un seul champ d'une union lors
de sa déclaration ?

--
Aurélien Regat-Barrel


Avatar
kanze
Aurelien Regat-Barrel wrote:

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_s tr.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.


Les unions anonymes, ça fait partie du langage. Les structs
anonymes, non, mais je crois que Microsoft n'est pas le seul à
les supporter comme extension. Seulement, avoir une struct
anonyme, et une autre non, et que les deux aient les mêmes noms
de membre... Ça me semble un peu louche.

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.


Deux structs anonymes ayant les mêmes noms de membre ne peut pas
aller. Logiquement. Si tu utilisais un des noms, de quelle
struct est-ce qu'il faut le tirer.

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__


Compatibilité Microsoft oblige:-).

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;


Formellement, non. Dans la pratique, oui. Mais pourquoi ?

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,


Ce n'est pas l'union en soi. C'est l'idée qu'on ferait un tel
michemache d'unions et de structs avec des noms aussi peu
parlants. Je n'en vois pas l'intérêt.

--
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
Sylvain
Loïc Joly wrote on 11/04/2006 08:16:

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



aucun de toute façon, BOOL est bien plus perçu comme un INT que comme un
(vrai) "bool", GetMessage() n'est pas la seule fonction retournant
toutes sortes de valeurs; la rédefinition (puisse qu'équivalente) aurait
été une option propre en effet.

Sylvain.

Avatar
Sylvain
James Kanze wrote on 09/04/2006 20:21:

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.)


une saloperie qui permets des déplacements dans un fichier proche du To
avec un compilo ne gérant que des longs 32 bits est imho une bonne
saloperie.

mais tes saloperies personnelles le font surement mieux (enfin, tu
l'affirmeras ... sans le détailler).

Sylvain.

Avatar
Sylvain
Aurelien Regat-Barrel wrote on 11/04/2006 10:48:

bool SetFilePointerEx( LONGLONG DistanceToMove )
{
LARGE_INTEGER tmp;
tmp.QuadPart = DistanceToMove;

return ::SetFilePointerEx( tmp ) == TRUE;
}

pour info j'ai retenu la 2° écriture, mais je me demandais si la
première était valable.


je préfère également cette seconde (car reinterpret_cast ne sert à
rien), voire:

bool setFilePointer(__int64 offset)
{
LARGE_INTEGER li;
li.QuadPart = offset;
return ::SetFilePointerEx(hFile, li, null, FILE_BEGIN) == TRUE);
}

__int64 n'est pas supporté par gcc (tjrs pas?) mais c'est tjrs plus
propre qu'un typedef M$ de plus; par ailleurs c'est bien ici le but que
de spécifier des offsets > 4 Go, un int64 en param. me parait + lisible
(faute de "long long").

Dernière question : peut-on initialiser un seul champ d'une union lors
de sa déclaration ?


c'est même ce qu'il faut faire - et non pas initialiser tous les champs
avec des valeurs non consistantes (eg initialiser à 0 les champs non
(effectivement) utilisés et à la valeur souhaitée le ou les autres).

avec un compilo M$ (et surement bcp d'autres), on a
sizeof(LARGE_INTEGER) = sizeof(QuadPart) = sizeof(u) = 8 donc modifier
un membre change forcement le membre aligné à la même adresse.

Sylvain.

Avatar
kanze
Aurelien Regat-Barrel wrote:

[...]
Formellement, non. Dans la pratique, oui. Mais pourquoi ?


Pour sucharger SetFilePointerEx ainsi par exemple:

bool SetFilePointerEx( LONGLONG DistanceToMove )
{
return ::SetFilePointerEx(
*reinterpret_cast<LARGE_INTEGER*>( &DistanceToMove )
) == TRUE;
}

au lieu de:

bool SetFilePointerEx( LONGLONG DistanceToMove )
{
LARGE_INTEGER tmp;
tmp.QuadPart = DistanceToMove;

return ::SetFilePointerEx( tmp ) == TRUE;
}

pour info j'ai retenu la 2° écriture, mais je me demandais si
la première était valable.


Formellement, non. Mais ce que tu fais ici dépend de toute façon
de l'implémentation. Alors, si l'implémentation le supporte...

Dernière question : peut-on initialiser un seul champ d'une
union lors de sa déclaration ?


Le premier seulement.

C99 a ajouté quelque chose qui s'appelle les initialisateurs
désignés, qui permet à spécifier le champ à initialiser. Autant
que je sache, c'est une extension que n'adopte pas le C++ -- le
C++ a d'autres besoins, et cherche une solution qui s'adopte à
des types définis par l'utilisateur aussi (mais j'espère qu'elle
couvrait ce cas-ci aussi).

--
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
kanze
Sylvain wrote:
Aurelien Regat-Barrel wrote on 11/04/2006 10:48:

bool SetFilePointerEx( LONGLONG DistanceToMove )
{
LARGE_INTEGER tmp;
tmp.QuadPart = DistanceToMove;

return ::SetFilePointerEx( tmp ) == TRUE;
}

pour info j'ai retenu la 2° écriture, mais je me demandais si la
première était valable.


je préfère également cette seconde (car reinterpret_cast ne
sert à rien), voire:

bool setFilePointer(__int64 offset)
{
LARGE_INTEGER li;
li.QuadPart = offset;
return ::SetFilePointerEx(hFile, li, null, FILE_BEGIN) == TRUE);
}

__int64 n'est pas supporté par gcc (tjrs pas?)


Pourquoi doit-il le supporter ? C'est un type propre à
l'implémentation Microsoft.

En fait, le comité C a adopté le « long long » des milieux Unix,
et le comité C++ suit -- « long long » fera partie de C++0x. Les
derniers compilateurs Microsoft l'acceptent déjà (comme la
quasi-totalité des compilateurs sous Unix).

mais c'est tjrs plus propre qu'un typedef M$ de plus; par
ailleurs c'est bien ici le but que de spécifier des offsets >
4 Go, un int64 en param. me parait + lisible (faute de "long
long").


Il n'y a plus de « faute de "long long" »:-). (Franchement, je
trouve qu'exceptionnellement, c'est Microsoft qui a fait le plus
proprement dans l'histoire. Mais c'est « long long » qui a été
retenu par le comité C, et le comité C++ n'a pas voulu
d'incompatibilités sur quelque chose d'aussi fondamental.)

Quant aux typedef en question : effectivement, ça n'a plus de
sens. Dans la mesure où le compilateur supportait __int64, à mon
avis, ça n'avait pas de sens alors non plus -- ça dépend de
toute façon de l'implémentation. (Il y a un truc curieux là
dedans aussi. Je me serait attendu que DWORD soit un type 64
bits -- sur des machines modernes, un « mot » c'est bien au
moins 32 bits. Et même bien avant... on avait BYTE (8 bits),
HWORD (16 bits), WORD (32 bits) et DWORD (32 bits). Au moins,
c'était l'utilisation courante à la fin des années 1970.)

Dernière question : peut-on initialiser un seul champ d'une
union lors de sa déclaration ?


c'est même ce qu'il faut faire - et non pas initialiser tous
les champs avec des valeurs non consistantes (eg initialiser à
0 les champs non (effectivement) utilisés et à la valeur
souhaitée le ou les autres).

avec un compilo M$ (et surement bcp d'autres), on a
sizeof(LARGE_INTEGER) = sizeof(QuadPart) = sizeof(u) = 8 donc
modifier un membre change forcement le membre aligné à la même
adresse.


J'ai 16 sur la moitié des machines à ma disposition:-). (8 sur
PC 32 bits Windows et PC 32 bits Linux, quelque soit le
compilateur. 16 sur PC 64 bits Linux ou Sun Sparc Solaris --
aussi quelque soit le compilateur.)

Étant donné que l'union en question fait partie de l'API
Windows, j'imagine que tous les compilateurs Windows s'arrangera
pour qu'elle ait la même organistation en mémoire. En revanche,
à part les PC, beaucoup de machines aujourd'hui sont à 64 bits,
avec un long de 8 octets (et qui a besoin d'un alignement de 8),
ce qui fait que le sizeof du struct est 16, et non 8.

--
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
kanze
Sylvain wrote:
James Kanze wrote on 09/04/2006 20:21:

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.)


une saloperie qui permets des déplacements dans un fichier
proche du To avec un compilo ne gérant que des longs 32 bits
est imho une bonne saloperie.


Il y a une éternité que Microsoft supporte des entiers 64 bits.
Sinon, quel est l'avantage de l'union sur une simple struct ?

--
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

je préfère également cette seconde (car reinterpret_cast ne sert à
rien), voire:


Je ne comprends pas ta remarque sur reinterpret_cast.

bool setFilePointer(__int64 offset)
{
LARGE_INTEGER li;
li.QuadPart = offset;
return ::SetFilePointerEx(hFile, li, null, FILE_BEGIN) == TRUE);
}

__int64 n'est pas supporté par gcc (tjrs pas?) mais c'est tjrs plus
propre qu'un typedef M$ de plus; par ailleurs c'est bien ici le but que
de spécifier des offsets > 4 Go, un int64 en param. me parait + lisible
(faute de "long long").


Je préfère LONGLONG qu'un type spécifique VC++. Si j'écris un wrapper
C++ à Win32 en utilisant les types Win32 plutôt que ceux de VC++, mon
wrapper sera plus facilement utilisable sur d'autres compilos. Si MingW
définit LONGLONG pour que ça marche, ça marchera.

Dernière question : peut-on initialiser un seul champ d'une union lors
de sa déclaration ?



c'est même ce qu'il faut faire - et non pas initialiser tous les champs
avec des valeurs non consistantes (eg initialiser à 0 les champs non
(effectivement) utilisés et à la valeur souhaitée le ou les autres).


Je parlais d'initialiser à la déclaration, et voulait savoir s'il était
possible (et comment) d'initialiser le 2° ou 3° champ d'une union, à la
déclaration toujours.

--
Aurélien Regat-Barrel


1 2 3 4 5