J'ai lu que la conversion d'un pointeur de fonction en un pointeur de données et vice versa fonctionne sur la plupart des plates-formes mais n'est pas garantie de fonctionner. pourquoi est-ce le cas? Les deux ne devraient-ils pas être simplement des adresses dans la mémoire principale et donc être compatibles?
c++
c
pointers
function-pointers
gexicide
la source
la source
void
. La conversion d'un pointeur de fonction envoid *
ne doit pas modifier la représentation. Unevoid *
valeur résultant d'une telle conversion peut être reconvertie dans le type de pointeur de fonction d'origine, à l'aide d'un cast explicite, sans perte d'informations. Remarque : la norme ISO C ne l'exige pas, mais elle est requise pour la conformité POSIX.dlsym()
- notez la fin de la section «Utilisation de l'application» où il est dit: Notez que la conversion d'unvoid *
pointeur en un pointeur de fonction comme dans:fptr = (int (*)(int))dlsym(handle, "my_function");
n'est pas définie par la norme ISO C. Cette norme requiert que cette conversion fonctionne correctement sur les implémentations conformes.Réponses:
Une architecture n'a pas à stocker le code et les données dans la même mémoire. Avec une architecture Harvard, le code et les données sont stockés dans une mémoire complètement différente. La plupart des architectures sont des architectures Von Neumann avec du code et des données dans la même mémoire, mais C ne se limite pas à certains types d'architectures si possible.
la source
CS != DS
).VirtualProtect
, ce qui vous permet de marquer des régions de données comme exécutables.Certains ordinateurs ont (avaient) des espaces d'adressage séparés pour le code et les données. Sur un tel matériel, cela ne fonctionne tout simplement pas.
Le langage est conçu non seulement pour les applications de bureau actuelles, mais pour lui permettre d'être implémenté sur un grand nombre de matériels.
Il semble que le comité du langage C n'ait jamais voulu
void*
être un pointeur vers une fonction, il voulait juste un pointeur générique vers des objets.La justification du C99 dit:
Remarque Rien n'est dit sur les pointeurs vers des fonctions dans le dernier paragraphe. Ils peuvent être différents des autres indicateurs, et le comité en est conscient.
la source
void *
.sizeof(void*) == sizeof( void(*)() )
gaspillerait de l'espace dans le cas où les pointeurs de fonction et les pointeurs de données sont de tailles différentes. C'était un cas courant dans les années 80, lorsque la première norme C a été écrite.Pour ceux qui se souviennent de MS-DOS, de Windows 3.1 et des versions antérieures, la réponse est assez simple. Tous ces éléments étaient utilisés pour prendre en charge plusieurs modèles de mémoire différents, avec différentes combinaisons de caractéristiques pour les pointeurs de code et de données.
Ainsi par exemple pour le modèle Compact (petit code, grandes données):
et inversement dans le modèle Medium (grand code, petites données):
Dans ce cas, vous n'aviez pas de stockage séparé pour le code et la date, mais vous ne pouviez toujours pas convertir entre les deux pointeurs (à moins d'utiliser des modificateurs __near et __far non standard).
De plus, il n'y a aucune garantie que même si les pointeurs sont de la même taille, ils pointent vers la même chose - dans le modèle de mémoire DOS Small, le code et les données sont utilisés près des pointeurs, mais ils pointent vers des segments différents. Ainsi, la conversion d'un pointeur de fonction en pointeur de données ne vous donnerait pas un pointeur ayant une quelconque relation avec la fonction, et par conséquent, une telle conversion n'était pas utile.
la source
int*
en unvoid*
vous donne un pointeur avec lequel vous ne pouvez vraiment rien faire, mais il est toujours utile de pouvoir effectuer la conversion. (C'est parce quevoid*
peut stocker n'importe quel pointeur d'objet, donc peut être utilisé pour des algorithmes génériques qui n'ont pas besoin de savoir quel type ils contiennent. La même chose pourrait être utile pour les pointeurs de fonction aussi, si elle était autorisée.)int *
versvoid *
, ilvoid *
est garanti qu'il pointe au moins vers le même objet que l'originalint *
- cela est donc utile pour les algorithmes génériques qui accèdent à l'objet pointé, commeint n; memcpy(&n, src, sizeof n);
. Dans le cas où la conversion d'un pointeur de fonction en avoid *
ne donne pas un pointeur pointant sur la fonction, ce n'est pas utile pour de tels algorithmes - la seule chose que vous pouvez faire est de reconvertir levoid *
retour en pointeur de fonction, vous pouvez donc Eh bien, utilisez simplement un pointeurunion
contenant unevoid *
fonction et.void*
fait pointer vers la fonction, je suppose que ce serait une mauvaise idée pour les gens de la transmettrememcpy
. :-Pvoid
. La conversion d'un pointeur de fonction envoid *
ne doit pas modifier la représentation. Unevoid *
valeur résultant d'une telle conversion peut être reconvertie dans le type de pointeur de fonction d'origine, à l'aide d'un cast explicite, sans perte d'informations. Remarque : la norme ISO C ne l'exige pas, mais elle est requise pour la conformité POSIX.Les pointeurs vers void sont censés pouvoir accueillir un pointeur vers n'importe quel type de données - mais pas nécessairement un pointeur vers une fonction. Certains systèmes ont des exigences différentes pour les pointeurs vers des fonctions et les pointeurs vers des données (par exemple, il existe des DSP avec un adressage différent pour les données par rapport au code, le modèle moyen sur MS-DOS utilise des pointeurs 32 bits pour le code mais uniquement des pointeurs 16 bits pour les données) .
la source
En plus de ce qui est déjà dit ici, il est intéressant de regarder POSIX
dlsym()
:la source
void*
.void*
d'être compatible avec un pointeur de fonction, contrairement à POSIX.C ++ 11 a une solution à la discordance de longue date entre C / C ++ et POSIX en ce qui concerne
dlsym()
. On peut utiliserreinterpret_cast
pour convertir un pointeur de fonction vers / à partir d'un pointeur de données tant que l'implémentation prend en charge cette fonctionnalité.D'après la norme, 5.2.10 par. 8, "la conversion d'un pointeur de fonction en un type de pointeur d'objet ou vice versa est conditionnellement prise en charge." 1.3.5 définit "pris en charge conditionnellement" comme une "construction de programme qu'une implémentation n'est pas tenue de prendre en charge".
la source
-Werror
). Une meilleure solution (et non-UB) est de récupérer un pointeur vers l'objet renvoyé pardlsym
(ievoid**
) et de le convertir en un pointeur en pointeur de fonction . Toujours défini par l'implémentation mais ne provoque plus d'avertissement / d'erreur .dlsym
etGetProcAddress
compiler sans avertissement.-pedantic
) n'avertit. Encore une fois, aucune spéculation n'est possible.En fonction de l'architecture cible, le code et les données peuvent être stockés dans des zones de mémoire fondamentalement incompatibles et physiquement distinctes.
la source
void *
est assez grand pour contenir n'importe quel pointeur de données, mais pas nécessairement n'importe quel pointeur de fonction.undefined ne signifie pas nécessairement non autorisé, cela peut signifier que l'implémenteur du compilateur a plus de liberté pour le faire comme il le souhaite.
Par exemple, cela peut ne pas être possible sur certaines architectures - undefined leur permet de conserver une bibliothèque 'C' conforme même si vous ne pouvez pas le faire.
la source
Une autre solution:
En supposant que POSIX garantit que les pointeurs de fonction et de données ont la même taille et la même représentation (je ne trouve pas le texte pour cela, mais l'exemple OP cité suggère qu'ils avaient au moins l' intention de faire cette exigence), ce qui suit devrait fonctionner:
Cela évite de violer les règles d'aliasing en passant par la
char []
représentation, qui est autorisée à aliaser tous les types.Encore une autre approche:
Mais je recommanderais l'
memcpy
approche si vous voulez absolument 100% correct C.la source
Ils peuvent être de différents types avec des exigences d'espace différentes. L'affectation à un peut découper de manière irréversible la valeur du pointeur de sorte que l'assignation en retour aboutisse à quelque chose de différent.
Je pense qu'ils peuvent être de types différents parce que la norme ne veut pas limiter les implémentations possibles qui économisent de l'espace lorsque ce n'est pas nécessaire ou lorsque la taille pourrait obliger le processeur à faire des conneries supplémentaires pour l'utiliser, etc.
la source
La seule solution vraiment portable consiste à ne pas utiliser
dlsym
pour les fonctions, mais plutôt à utiliserdlsym
pour obtenir un pointeur vers des données contenant des pointeurs de fonction. Par exemple, dans votre bibliothèque:puis dans votre application:
Incidemment, c'est de toute façon une bonne pratique de conception, et il est facile de prendre en charge à la fois le chargement dynamique via
dlopen
et la liaison statique de tous les modules sur des systèmes qui ne prennent pas en charge la liaison dynamique, ou lorsque l'intégrateur utilisateur / système ne souhaite pas utiliser de liaison dynamique.la source
foo_module
structure (avec des noms uniques), vous pouvez simplement créer un fichier supplémentaire avec un tableau destruct { const char *module_name; const struct module *module_funcs; }
et une fonction simple pour rechercher dans cette table le module que vous voulez "charger" et renvoyer le bon pointeur, puis utilisez ceci à la place dedlopen
etdlsym
.Un exemple moderne où les pointeurs de fonction peuvent différer en taille des pointeurs de données: pointeurs de fonction membre de classe C ++
Directement cité de https://blogs.msdn.microsoft.com/oldnewthing/20040209-00/?p=40713/
tl; dr: Lors de l'utilisation de l'héritage multiple, un pointeur vers une fonction membre peut (selon le compilateur, la version, l'architecture, etc.) être stocké comme
qui est évidemment plus grand qu'un
void *
.la source
Sur la plupart des architectures, les pointeurs vers tous les types de données normaux ont la même représentation, donc la conversion entre les types de pointeurs de données est un no-op.
Cependant, il est concevable que les pointeurs de fonction nécessitent une représentation différente, peut-être qu'ils sont plus grands que les autres pointeurs. Si void * pouvait contenir des pointeurs de fonction, cela signifierait que la représentation de void * devrait avoir la plus grande taille. Et tous les moulages de pointeurs de données vers / depuis void * devraient effectuer cette copie supplémentaire.
Comme quelqu'un l'a mentionné, si vous en avez besoin, vous pouvez y parvenir en utilisant un syndicat. Mais la plupart des utilisations de void * sont juste pour les données, il serait donc onéreux d'augmenter toute leur utilisation de la mémoire juste au cas où un pointeur de fonction doit être stocké.
la source
Je sais que cela n'a pas été commenté depuis 2012, mais je pensais que ce serait utile d'ajouter que je ne sais une architecture qui a très pointeurs incompatibles pour les données et les fonctions depuis un appel en ce privilège vérifie l' architecture et porte des informations supplémentaires. Aucune quantité de casting n'aidera. C'est le moulin .
la source