Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Interchangeabilité des types pointeur

18 réponses
Avatar
Jérôme Frgacic
Bonjour =E0 tous,

Tout d'abord, meilleurs v=9Cux pour cette ann=E9e 2013.

Dans le cas d'un petit projet personnel, je souhaite cr=E9e une liste dont =
chaque n=9Cud comporte un pointeur vers une ressource allou=E9e et un point=
eur de fonction sp=E9cifiant la fonction permettant de lib=E9rer cette ress=
ource. Ainsi, j'ai repr=E9sent=E9 un n=9Cud =E0 l'aide de cette structure :

struct ressource {
void *data;
void (*delete)();
struct ressource * next;
};

Le probl=E8me, c'est que les ressources allou=E9es sont stock=E9es au sein =
d'un pointeur g=E9n=E9rique (pointeur sur void), mais que les fonctions de =
destructions r=E9f=E9renc=E9es, elles, n'attendent pas forc=E9ment un tel p=
ointeur (parfois oui, comme free, mais parfois non, comme fclose ou autre).=
Je sais que, =E0 ce sujet, la norme fourni quelques garanties pour certain=
s types de pointeurs :

C11 (n1570), =A7 6.2.5 Types, al. 28, p. 43
> A pointer to void shall have the same representation and alignment
> requirements as a pointer to a character type. Similarly, pointers
> to qualified or unqualified versions of compatible types shall have
> the same representation and alignment requirements. All pointers to
> structure types shall have the same representation and alignment
> requirements as each other. All pointers to union types shall have the
> same representation and alignment requirements as each other. Pointers
> to other types need not have the same representation or alignment
> requirements.

mais pour le reste, rien n'est pr=E9vu. Aussi, j'aurais voulu savoir si j'a=
i des chances de tomber sur des architectures o=F9 une telle solution pose =
probl=E8me et, =E9galement, s'il existe une autre solution, cette fois-ci p=
ortable ?=20

Merci d'avance pour vos r=E9ponses.

8 réponses

1 2
Avatar
Antoine Leca
Jérôme Frgacic écrivit :
Antoine Leca a écrit :
Euh, si. 6.3 /Conversions/, § 6.3.2.3 /Pointers/, al. 1 :
A pointer to void may be converted to or from a pointer to any
object type. A pointer to any object type may be converted to
a pointer to void and back again; the result shall compare
equal to the original pointer.

En gros, tant que ton code manipule des pointeurs génériques et que
derrière il y a des pointeurs vers des types _objets_, tout va bien.
Il faut bien comprendre que si dans ton code tu écris
FILE* f;
ptr_ress‐>data = f;
il y a conversion de FILE* à void* ; et quand tu écris plus tard
fclose( (FILE*)(ptr_ress‐>data) );
la conversion de retour (/back/) est même explicite !



En fait, mon problème est que la libération de la ressource est faite
via le pointeur de fonction « delete » de la structure. Autrement dit,
pour chaque ressource, on a un appel du type :

ptr_ress‐>delete(ptr_ress‐>data);



Halte au hold-up !

Je reprend ta déclaration initiale :

struct ressource {
void *data;
void (*delete)();
struct ressource * next;
};

Le problème, c'est que tu utilises un prototype générique pour "delete"
Autrement dit, il n'y a plus aucun contrôle, tu peux tout aussi bien appeler
ptr_ress‐>delete(42);


Je croyais que ton idée, c'était d'écrire

struct ressource {
void *data;
void (*delete)(void*);
struct ressource * next;
};

et de coder les adjudants «delete», par exemple pour la mémoire
void delete_memoire(void*p) { free(p); }
ou pour les fichiers
void delete_fichier(void*f) { fclose(p); }

Cette dernière fonction va effectuer la conversion de type si besoin
est, et va aussi va permettre d'ignorer la différence de type des
valeurs de retour: la signature de &fclose est int(*)(), pas void(*)()


[couic]
Mais, si je comprend bien, dans la réalité, tous les pointeurs ont
généralement la même taille sauf les pointeurs de fonctions ?



Sauf machines vraiment bizarres, oui. Les exemples les plus classiques
des cas contraires sont d'une part les machines genre PDP10 ou certains
Unisys à adressage mot (donc un pointeur vers char est plus gros car il
faut y ajouter un sous-pointeur vers le byte au sein du mot), et d'autre
part les adressages «segmentés» genre iAPX286 en mode H où chaque
structure utilise son propre segment.

De plus, un problème avec les pointeurs de fonctions, au delà d'une
taille différente, ils peuvent être incommensurables, opérant dans un
espace d'adresses différent, avec un jeu d'instructions différentes.


Antoine
Avatar
J
Marc Espie a écrit :
Tu confonds le pointeur et l’objet pointe, pour la partie alignem ent.



Il me semble que ce passage traîte bien de l’alignement des ty pes
pointeur et non des types pointés. En effet, si cela n’ét ait pas le
cas, on en déduirait que toutes les structures doivent avoir le mà ªme
alignement, de même pour les unions, or ce n’est à ma con naissance pas
le cas.

Antoine Leca a écrit :
Le problème, c’est que tu utilises un prototype génà ©rique pour
"delete" Autrement dit, il n’y a plus aucun contrôle, tu peu x tout
aussi bien appeler

ptr_ress‐>delete(42);




Oui, j’avais choisit ce type de pointeur pour éviter de multip les
conversions explicites et aussi pour éviter cette création «
d’adjudants ». Mais, au final, je ne me rend compte que ce n ’est pas
aussi bien que cela.

Antoine Leca a écrit :
Sauf machines vraiment bizarres, oui. Les exemples les plus classiques
des cas contraires sont d’une part les machines genre PDP10 ou
certains Unisys à adressage mot (donc un pointeur vers char est plus
gros car il faut y ajouter un sous‐pointeur vers le byte au sein du
mot), et d’autre part les adressages «segmentés» g enre iAPX286 en mode
H où chaque structure utilise son propre segment.



Ok, c’est ce dont je voulais avant tout m’assurer.
Merci pour cette précision. ;)
Avatar
Jean-Marc Bourguet
Jérôme Frgacic writes:

Antoine Leca a écrit :
Sauf machines vraiment bizarres, oui. Les exemples les plus classiques
des cas contraires sont d’une part les machines genre PDP10 ou
certains Unisys à adressage mot (donc un pointeur vers char est plus
gros car il faut y ajouter un sous‐pointeur vers le byte au sein du
mot), et d’autre part les adressages «segmentés» genre iAPX286 en mode
H où chaque structure utilise son propre segment.



Ok, c’est ce dont je voulais avant tout m’assurer.
Merci pour cette précision. ;)



Il y a aussi le probleme des optimiseurs.

Il y a deux points de vue sur les comportements indefinis: ils sont la
pour traiter des cas particuliers et en dehors de ceux-ci il faut agir
de maniere sensee; ils sont reellement la porte ouverte a tout. Ceux
qui ecrivent les optimiseurs ont tendance a avoir le second.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
espie
In article <kcjjnh$ikl$,
Antoine Leca wrote:
Jérôme Frgacic écrivit :
[couic]
Mais, si je comprend bien, dans la réalité, tous les pointeurs ont
généralement la même taille sauf les pointeurs de fonctions ?



Sauf machines vraiment bizarres, oui. Les exemples les plus classiques
des cas contraires sont d'une part les machines genre PDP10 ou certains
Unisys à adressage mot (donc un pointeur vers char est plus gros car il
faut y ajouter un sous-pointeur vers le byte au sein du mot), et d'autre
part les adressages «segmentés» genre iAPX286 en mode H où chaque
structure utilise son propre segment.



Attention, je crois qu'on melange deux trucs.

Jerome se demande si ce qu'il fait fonctionne avec du C standard de
facon portable.

Et les trucs dont tu parles sortent du C standard.

Plus exactement, il y a la possibilite a des implementations d'avoir
des trucs "implementation-defined" pour certains adressages (voire d'avoir
des archis suffisamment exotiques pour qu'il y ait des implementations de
C qui ne se conforment pas completement a la norme).

Mais, de ce que je sais du C, dans une implementation conforme, il n'y a pas
de souci avec les pointeurs.

Les seuls pointeurs "bizarres" sont explicites, c'est les trucs comme
__far et __near sur certaines implementations. La norme couvre ces cas,
mais tu ne vas pas te retrouver avec ceux-ci "par surprise". Tu n'es plus
dans la partie strictement conforme, tu es dans la partie
"implementation-defined", et la norme, car c'est son boulot, te dit que
tout marche bien tant que tu restes dans les clous.
Avatar
Jean-Marc Bourguet
(Marc Espie) writes:

In article <kcjjnh$ikl$,
Antoine Leca wrote:
Jérôme Frgacic écrivit :
[couic]
Mais, si je comprend bien, dans la réalité, tous les pointeur s ont
généralement la même taille sauf les pointeurs de foncti ons ?



Sauf machines vraiment bizarres, oui. Les exemples les plus classiques
des cas contraires sont d'une part les machines genre PDP10 ou certains
Unisys à adressage mot (donc un pointeur vers char est plus gros car il
faut y ajouter un sous-pointeur vers le byte au sein du mot), et d'autre
part les adressages «segmentés» genre iAPX286 en mode H o ù chaque
structure utilise son propre segment.



Attention, je crois qu'on melange deux trucs.

Jerome se demande si ce qu'il fait fonctionne avec du C standard de
facon portable.


Et les trucs dont tu parles sortent du C standard.



Pas d'accord: 2.6.5/28 (C 11 mais je suis sur que ce n'est pas quelque
chose de nouveau)

A pointer to void shall have the same representation and alignment
requirements as a pointer to a character type. Similarly, pointers to
qualified or unqualified versions of compatible types shall have the
same representation and alignment requirements. All pointers to
structure types shall have the same representation and alignment
requirements as each other. All pointers to union types shall have the
same representation and alignment requirements as each other. Pointers
to other types need not have the same representation or alignment
requirements.

void* peut avoir une représentation différente de FILE*, et passe r un
void* a une fonction qui s'attend a un FILE* sans prototype declanchant
une conversion implicite est un UB sur ces machines. (Ces
implementations sont annectotiques de nos jours, les rares cas modernes
dont j'ai connaissance qui sont adressables par mots choisissent d'avoir
sizeof(char)==sizeof(int)==1 plutot que d'avoir sizeof(int)>1 et
sizeof(char*)>sizeof(int*) -- de plus si j'ai bonne memoire ce n'est pas
conforme a POSIX).

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Jean-Marc Bourguet
Jean-Marc Bourguet writes:

void* peut avoir une représentation différente de FILE*, et pas ser un
void* a une fonction qui s'attend a un FILE* sans prototype declanchant
une conversion implicite est un UB sur ces machines.



Je me demande si le comportement est defini ou pas sur une
implementation qui choisit d'avoir la meme representation pour tous les
pointeurs; il faudrait rechercher l'ensemble des references pertinentes
et j'ai pas le souvenir de l'avoir deja fait -- pire, ca me semble
toucher a des zones ou les differences de formulation entre C90, C99 (et
peut-etre C11) ne sont pas minimes.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Antoine Leca
Marc Espie écrivit :
Attention, je crois qu'on melange deux trucs.



C'est bien possible, merci de recentrer.


Jerome se demande si ce qu'il fait fonctionne avec du C standard de
facon portable.



Et justement, je ne trouve que ce soit une très bonne idée.

Pour mémoire, il voudrait manipuler des fonctions prenant des types
pointeurs variés en paramètre, _sans_ utiliser de prototypes.

Ne pas utiliser de prototype est effectivement du C standard, mais je ne
suis pas sûr que ce soit une très bonne solution pour assurer la
portabilité.

D'un autre côté, en termes de perfs, je ne sais pas si les compilateurs
actuels sont assez finaux pour remplacer
void delete_memoire(void*p) { free(p); }
void delete_fichier(void*f) { fclose(p); }
par
.alias delete_memoire, free
.alias delete_fichier, fclose
et éviter l'indirection « inutile ».


Après, et c'est là que l'on mélange, il est vrai qu'avec la
quasi-totalité des machines actuelles, le seul effet de tout cela sera
des avertissements sans frais (même avec des prototypes).
Et il est bien clair que la tendance va dans le sens d'avoir de moins en
moins de ces cas particuliers, et au contraire d'avoir tous les
pointeurs (vers objets) avec des représentations identiques.


De plus, un clang récent (3.1) proteste de la manière ("incompatible
pointer type") quand j'écris du code qui va marcher « partout », comme

int (*delete_fichier)(void*) = fclose;

ou si j'écris du code beaucoup plus tendancieux, genre

int (*delete_memoire)() = free;
/* ... */
if( delete_memoire(&ressource) != 0 ) /* ta-da */

alors même que dans le second cas, l'appel ultérieur avec utilisation de
la valeur retournée appelle au meurtre...


Antoine
Avatar
Jérôme Frgacic
Merci à tous pour vos réponses et vos explications, me voilà fixé s ur cette interrogation. :)
1 2