38 votes

Pourquoi ce code C fonctionne-t-il?

Dans ANSI C, offsetof est défini comme ci-dessous.

 #define offsetof(st, m) \
    ((size_t) ( (char *)&((st *)(0))->m - (char *)0 ))
 

Pourquoi cela ne déclenche-t-il pas une erreur de segmentation puisque nous déréférençons un pointeur NULL? Ou est-ce une sorte de piratage du compilateur où il voit que seule l'adresse du décalage est supprimée, de sorte qu'il calcule statiquement l'adresse sans vraiment la déréférencer? Ce code est-il également portable?

39voto

JaredPar Points 333733

À aucun moment dans le code ci-dessus est tout déréférencé. Un déréférencement de se produit lorsque l' * ou> est utilisée en tant qu'une valeur d'adresse pour trouver la valeur de référence. La seule utilisation d' * ci-dessus est une déclaration de type pour la fin de la coulée.

L' -> opérateur est utilisé ci-dessus, mais il n'est pas utilisé pour accéder à la valeur. Au lieu de cela, elle est utilisée pour saisir l'adresse de la valeur. Voici une macro exemple de code qui devrait le rendre un peu plus clair

SomeType *pSomeType = GetTheValue();
int* pMember = &(pSomeType->SomeIntMember);

La deuxième ligne n'est pas vraiment un déréférencement (dépendant de l'implémentation). Il renvoie simplement l'adresse de SomeIntMember dans le pSomeType valeur.

Ce que vous voyez est un beaucoup de la conversion entre les types arbitraires et pointeurs de char. La raison de char, c'est que c'est le seul type (peut-être le seul) type dans la norme C89 qui a une taille explicite. La taille est de 1. En assurant la taille est un, le code ci-dessus peut faire le mal, la magie de calcul de la véritable décalage de la valeur.

10voto

Jonathan Leffler Points 299946

Bien que ce est typique de mise en œuvre de l' offsetof, il n'est pas exigé par la norme, qui dit tout simplement:

Les types suivants et les macros sont définies dans la norme en-tête <stddef.h> [...]

    offsetof(type, member-designator)

qui se développe à une constante entière expression de type size_t, la valeur de qui est le décalage en octets, pour le membre de structure (désigné par les états-indicateur), depuis le début de sa structure (désigné par type). Le type et membre de désignation doit être telle que, compte tenu de

    static type t;

ensuite, l'expression &(t.membre-indicateur) évalue à une adresse constante. (Si le membre spécifié est un peu sur le terrain, le comportement est indéfini.)

Lire P J Plauger "de La Bibliothèque Standard C" pour une discussion à ce sujet et d'autres articles en <stddef.h> qui sont toutes les fonctionnalités de ligne qui pourrait (devrait?) être dans la langue appropriée, et qui peut nécessiter la prise en charge du compilateur.

C'est de l'historique, de l'intérêt seulement, mais j'ai utilisé un début de compilateur C ANSI sur 386/IX (voyez, je vous l'ai dit un intérêt historique, circa 1990) qui s'est écrasé sur cette version de l' offsetof , mais il a travaillé quand j'ai révisé à:

#define offsetof(st, m) ((size_t)((char *)&((st *)(1024))->m - (char *)1024))

C'était un bug du compilateur de toutes sortes, pas moins parce que l'en-tête a été distribué avec le compilateur et n'a pas de travail.

9voto

MSalters Points 74024

Dans ANSI C, offsetof n'est PAS défini comme ça. L'une des raisons pour lesquelles il n'est pas défini de cette manière est que certains environnements lèveront en effet des exceptions de pointeur nul ou se bloqueront d'une autre manière. Par conséquent, ANSI C laisse l'implémentation de offsetof( ) ouverte aux constructeurs de compilateurs.

Le code ci-dessus est typique pour les compilateurs / environnements qui ne vérifient pas activement les pointeurs NULL, mais échouent uniquement lorsque les octets sont lus à partir d'un pointeur NULL.

8voto

sigjuice Points 9166

Pour répondre à la dernière partie de la question, le code n'est pas portable.

Le résultat de la soustraction de deux pointeurs est défini et portable uniquement si les deux pointeurs pointent vers des objets dans le même tableau ou pointent vers un dernier le dernier objet du tableau (7.6.2 Opérateurs additifs, H&S cinquième édition)

2voto

Cătălin Pitiș Points 10520

Il calcule le décalage du membre m par rapport à l'adresse de début de la représentation d'un objet de type st.

((st *)(0)) se réfère à un NULL pointeur de type st *. &((st *)(0))->m fait référence à l'adresse du membre de la m dans cet objet. Depuis le début de l'adresse de cet objet est - 0 (NULL), l'adresse de membre m est exactement le décalage.

char * de conversion et la différence calcule le décalage en octets. Selon les opérations de pointeur, quand vous faites une différence entre les deux pointeurs de type T *, le résultat est le nombre d'objets de type T représenté entre les deux adresses contenues par les opérandes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X