Quel est l’effet de vider un pointeur de fonction?

Donc, j’essaye d’écrire une bibliothèque de tampons pour la 64ème fois et je commence à me lancer dans des choses assez avancées. Je pensais que je demanderais une consortingbution professionnelle à ce sujet.

Dans mon premier fichier d’en-tête, j’ai ceci:

typedef struct StdBuffer { void* address; } StdBuffer; extern void StdBufferClear(StdBuffer); 

Dans un autre fichier d’en-tête qui #includes le premier fichier d’en-tête, j’ai ceci:

 typedef struct CharBuffer { char* address; } CharBuffer; void (*CharBufferClear)(CharBuffer) = (void*) StdBufferClear; 

La déclaration de l’annulation de ce pointeur de fonction interférera-t-elle avec l’appel? Ils ont des signatures de correspondance par valeur. Je n’ai jamais vu un pointeur de fonction déclaré vide auparavant, mais c’est le seul moyen de le faire comstackr proprement.

Stackwise, cela ne devrait faire aucune différence par rapport à ce que j’ai appris sur le codage en assembleur.

non pertinent OMG! Je viens de dire Stackwise sur StackOverflow!

Hmm .. On dirait que j’ai trop présumé ici. Permettez-moi de déclarer si je peux. Je me fiche de savoir quel type de données est stocké à l’adresse. Tout ce qui me préoccupe est la taille d’une “unité” et le nombre d’unités à l’adresse. Jetez un coup d’œil au contrat de contrat d’interface pour l’API si vous voulez:

 typedef struct StdBuffer { size_t width; ///< The number of bytes that complete a data unit. size_t limit; ///< The maximum number of data units that can be allocated for this buffer. void * address; ///< The memory address for this buffer. size_t index; ///< The current unit position indicator. size_t allocated; ///< The current number of allocated addressable units. StdBufferFlags flags;///< The API contract for this buffer. } StdBuffer; 

Vous voyez, memcpy, memmove et autres ne se soucient pas vraiment de savoir quoi à une adresse, tout ce qu’ils veulent, ce sont les détails que je garde clairement en mémoire ici.

Regardez maintenant le premier prototype à suivre ce contrat:

 typedef struct CharBuffer { size_t width; ///< The number of bytes that complete a data unit. size_t limit; ///< The maximum number of data units that can be allocated for this buffer. char * address; ///< The memory address for this buffer. size_t index; ///< The current unit position indicator. size_t allocated; ///< The current number of allocated addressable units. CharBufferFlags flags;///< The API contract for this buffer. } CharBuffer; 

Comme vous le voyez clairement, le type de données n’est pas pertinent dans ce contexte. Vous pouvez dire que C le gère différemment selon les cas, mais en fin de journée, une address est une address , un byte est un byte et un long est un long tant que nous avons affaire à de la mémoire sur la même machine. .

Le but de ce système, une fois mis en place, est de supprimer tout ce type de jonglage basé sur C qui semble être si fier (et à juste titre …). Cela ne sert à rien pour ce que je voudrais faire. Ce qui est de créer un prototype de contrat pour toutes les tailles de données standard (1, 2, 4, 8, sizeof (RandomStruct)) situées à n’importe quelle adresse.

Avoir la possibilité d’effectuer ma propre conversion avec du code et de manipuler ces données avec des fonctions api qui fonctionnent sur des blocs de mémoire de longueur spécifique avec des unités de mémoire de longueur spécifique. Cependant, le prototype doit contenir le type de pointeur de données officiel, car il n’a aucun sens de demander à l’utilisateur final de refondre ses données à chaque fois qu’il souhaite effectuer quelque chose avec ce pointeur d’adresse. Il ne serait pas logique d’appeler cela un CharBuffer si le pointeur était vide.

StdBuffer est un type générique qui n’est JAMAIS utilisé, sauf dans l’API elle-même, pour gérer tous les types de données respectant les contrats.

L’API que ce système incorporera provient de ma dernière édition de mise en mémoire tampon. @Google Code Je suis conscient du fait que certaines choses devront être modifiées pour que tout soit mis en place, à savoir que je ne serai pas en mesure de manipuler les données directement à partir de l’API en toute sécurité sans beaucoup de recherches appropriées et de collecte d’opinion.

Ce qui vient d’attirer l’attention sur le fait que j’ai également besoin d’un indicateur de bit Signed / Unsigned dans les membres StdBufferFlags.

Peut-être que la pièce finale de ce puzzle est également en ordre pour votre lecture.

 /** \def BIT(I) \brief A macro for setting a single constant bit. * * This macro sets the bit indicated by I to enabled. * \param I the (1-based) index of the desired bit to set. */ #define BIT(I) (1UL << (I - 1)) /** \enum StdBufferFlags \brief Flags that may be applied to all StdBuffer structures. * These flags determine the contract of operations between the caller * and the StdBuffer API for working with data. Bits 1-4 are for the * API control functions. All other bits are undefined/don't care bits. * * If your application would like to use the don't care bits, it would * be smart not to use bits 5-8, as these may become used by the API * in future revisions of the software. */ typedef enum StdBufferFlags { BUFFER_MALLOCD = BIT(1), ///< The memory address specified by this buffer was allocated by an API BUFFER_WRITEABLE = BIT(2), ///< Permission to modify buffer contents using the API BUFFER_READABLE = BIT(3), ///< Permission to retrieve buffer contents using the API BUFFER_MOVABLE = BIT(4) ///< Permission to resize or otherwise relocate buffer contents using the API }StdBufferFlags; 

Ce code nécessite un diagnostic:

 void (*CharBufferClear)(CharBuffer) = (void*) StdBufferClear; 

Vous convertissez un pointeur void * un pointeur de fonction sans transtypage. En C, un pointeur void * peut être converti en pointeurs en types d’object sans transtypage, mais pas en types de pointeur. (En C ++, une conversion est nécessaire pour convertir void * en types d’object également, pour plus de sécurité.)

Ce que vous voulez ici, c’est juste pour lancer entre les types de pointeurs de fonction, à savoir:

 void (*CharBufferClear)(CharBuffer) = (void (*)(CharBuffer)) StdBufferClear; 

Ensuite, vous faites toujours le même type punning car les fonctions sont de types différents. Vous essayez d’appeler une fonction qui prend un StdBuffer aide d’un pointeur sur une fonction qui prend un CharBuffer .

Ce type de code n’est pas bien défini. Une fois que vous avez vaincu le système de types, vous êtes autonome: vous pouvez tester, examiner le code object ou obtenir des auteurs de la compilation que ceux-ci garantissent que ce genre de chose fonctionne avec ce compilateur. .

Ce que vous avez appris dans le codage en assembleur ne s’applique pas, car les langages d’assemblage ne comportent qu’un petit nombre de types de données rudimentaires tels que “adresse de la machine” ou “mot de 32 bits”. Le concept selon lequel deux structures de données avec une disposition identique et une représentation de bas niveau pourraient être des types incompatibles n’apparaît pas dans le langage assembleur.

Même si deux types se ressemblent au niveau le plus bas (un autre exemple: unsigned int et unsigned long sont parfois exactement les mêmes), les compilateurs C peuvent optimiser les programmes en supposant que les règles de type n’ont pas été violées. Par exemple, supposons que A et B pointent vers le même emplacement mémoire. Si vous assignez à un object A->member , un compilateur C peut supposer que l’object B->member n’est pas affecté par cela, si A->member et B->member ont des types incompatibles, comme par exemple char * et l’autre void * . Le code généré continue de mettre en cache l’ancienne valeur de B->member dans un registre, même si la copie en mémoire a été écrasée par l’affectation à A->member . Ceci est un exemple d’ alias invalide.

La norme ne définit pas les résultats de la conversion d’un pointeur de fonction sur void * .

De même, la conversion entre les pointeurs de fonction et ensuite l’appel du mauvais est également un comportement indéfini.

Tous les compilateurs C conformes aux normes doivent implémenter certaines constructions de manière cohérente, et 99% des compilateurs C les implémentent de manière cohérente, mais les compilateurs conformes aux normes seraient libres de les implémenter différemment. Tenter de convertir un pointeur en une fonction qui prend un type de pointeur, en un pointeur en une fonction qui prend un autre type de pointeur, tombe dans cette dernière catégorie. Bien que la norme C spécifie qu’un void* et un char* doivent avoir la même taille, rien ne demanderait qu’ils partagent le même format de stockage au niveau du bit, encore moins la convention de passage de parameters. Alors que la plupart des machines permettent d’accéder aux octets de la même manière que les mots, cette capacité n’est pas universelle. Le concepteur d’une interface binary d’application [le document spécifiant, entre autres, le mode de transmission des parameters aux routines] peut spécifier qu’un caractère char* être transmis de manière à optimiser l’efficacité de l’access par octets, tandis qu’un void* doit être transmis. de manière à maximiser l’efficacité de l’access aux mots tout en conservant la possibilité de conserver une adresse d’octet non alignée, éventuellement en utilisant un mot supplémentaire pour un zéro ou un pour indiquer un LSB / MSB). Sur une telle machine, le fait d’avoir une routine qui attend un void* appelé à partir d’un code qui s’attend à transmettre un caractère char* peut amener la routine à accéder à des données erronées arbitraires.

Non, le type de données utilisé pour stocker les données importe peu. Seul le type C utilise pour lire et écrire ces données, et leur taille est suffisante.